mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-09 20:11:13 +02:00
Merge branch 'master' of https://github.com/worklist/hifi
This commit is contained in:
commit
c276523afd
48 changed files with 1354 additions and 515 deletions
|
@ -643,7 +643,7 @@ void AudioMixer::run() {
|
||||||
QJsonObject audioGroupObject = settingsObject[AUDIO_GROUP_KEY].toObject();
|
QJsonObject audioGroupObject = settingsObject[AUDIO_GROUP_KEY].toObject();
|
||||||
|
|
||||||
// check the payload to see if we have asked for dynamicJitterBuffer support
|
// 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();
|
_streamSettings._dynamicJitterBuffers = audioGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool();
|
||||||
if (_streamSettings._dynamicJitterBuffers) {
|
if (_streamSettings._dynamicJitterBuffers) {
|
||||||
qDebug() << "Enable dynamic jitter buffers.";
|
qDebug() << "Enable dynamic jitter buffers.";
|
||||||
|
@ -652,21 +652,21 @@ void AudioMixer::run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok;
|
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);
|
_streamSettings._staticDesiredJitterBufferFrames = audioGroupObject[DESIRED_JITTER_BUFFER_FRAMES_KEY].toString().toInt(&ok);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
_streamSettings._staticDesiredJitterBufferFrames = DEFAULT_STATIC_DESIRED_JITTER_BUFFER_FRAMES;
|
_streamSettings._staticDesiredJitterBufferFrames = DEFAULT_STATIC_DESIRED_JITTER_BUFFER_FRAMES;
|
||||||
}
|
}
|
||||||
qDebug() << "Static desired jitter buffer frames:" << _streamSettings._staticDesiredJitterBufferFrames;
|
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);
|
_streamSettings._maxFramesOverDesired = audioGroupObject[MAX_FRAMES_OVER_DESIRED_JSON_KEY].toString().toInt(&ok);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
_streamSettings._maxFramesOverDesired = DEFAULT_MAX_FRAMES_OVER_DESIRED;
|
_streamSettings._maxFramesOverDesired = DEFAULT_MAX_FRAMES_OVER_DESIRED;
|
||||||
}
|
}
|
||||||
qDebug() << "Max frames over desired:" << _streamSettings._maxFramesOverDesired;
|
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();
|
_streamSettings._useStDevForJitterCalc = audioGroupObject[USE_STDEV_FOR_DESIRED_CALC_JSON_KEY].toBool();
|
||||||
if (_streamSettings._useStDevForJitterCalc) {
|
if (_streamSettings._useStDevForJitterCalc) {
|
||||||
qDebug() << "Using Philip's stdev method for jitter calc if dynamic jitter buffers enabled";
|
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";
|
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);
|
_streamSettings._windowStarveThreshold = audioGroupObject[WINDOW_STARVE_THRESHOLD_JSON_KEY].toString().toInt(&ok);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
_streamSettings._windowStarveThreshold = DEFAULT_WINDOW_STARVE_THRESHOLD;
|
_streamSettings._windowStarveThreshold = DEFAULT_WINDOW_STARVE_THRESHOLD;
|
||||||
}
|
}
|
||||||
qDebug() << "Window A starve threshold:" << _streamSettings._windowStarveThreshold;
|
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);
|
_streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = audioGroupObject[WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES_JSON_KEY].toString().toInt(&ok);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
_streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES;
|
_streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES;
|
||||||
}
|
}
|
||||||
qDebug() << "Window A length:" << _streamSettings._windowSecondsForDesiredCalcOnTooManyStarves << "seconds";
|
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);
|
_streamSettings._windowSecondsForDesiredReduction = audioGroupObject[WINDOW_SECONDS_FOR_DESIRED_REDUCTION_JSON_KEY].toString().toInt(&ok);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
_streamSettings._windowSecondsForDesiredReduction = DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION;
|
_streamSettings._windowSecondsForDesiredReduction = DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION;
|
||||||
}
|
}
|
||||||
qDebug() << "Window B length:" << _streamSettings._windowSecondsForDesiredReduction << "seconds";
|
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();
|
_streamSettings._repetitionWithFade = audioGroupObject[REPETITION_WITH_FADE_JSON_KEY].toBool();
|
||||||
if (_streamSettings._repetitionWithFade) {
|
if (_streamSettings._repetitionWithFade) {
|
||||||
qDebug() << "Repetition with fade enabled";
|
qDebug() << "Repetition with fade enabled";
|
||||||
|
@ -703,13 +703,13 @@ void AudioMixer::run() {
|
||||||
qDebug() << "Repetition with fade disabled";
|
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();
|
_printStreamStats = audioGroupObject[PRINT_STREAM_STATS_JSON_KEY].toBool();
|
||||||
if (_printStreamStats) {
|
if (_printStreamStats) {
|
||||||
qDebug() << "Stream stats will be printed to stdout";
|
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()) {
|
if (audioGroupObject[FILTER_KEY].isBool()) {
|
||||||
_enableFilter = audioGroupObject[FILTER_KEY].toBool();
|
_enableFilter = audioGroupObject[FILTER_KEY].toBool();
|
||||||
}
|
}
|
||||||
|
@ -717,7 +717,7 @@ void AudioMixer::run() {
|
||||||
qDebug() << "Filter enabled";
|
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();
|
QString unattenuatedZoneString = audioGroupObject[UNATTENUATED_ZONE_KEY].toString();
|
||||||
if (!unattenuatedZoneString.isEmpty()) {
|
if (!unattenuatedZoneString.isEmpty()) {
|
||||||
|
|
|
@ -3,14 +3,37 @@ set(TARGET_NAME domain-server)
|
||||||
# setup the project and link required Qt modules
|
# setup the project and link required Qt modules
|
||||||
setup_hifi_project(Network)
|
setup_hifi_project(Network)
|
||||||
|
|
||||||
# remove and then copy the files for the webserver
|
# remove current resources dir
|
||||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
|
add_custom_command(
|
||||||
COMMAND "${CMAKE_COMMAND}" -E remove_directory
|
TARGET ${TARGET_NAME} POST_BUILD
|
||||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/web)
|
COMMAND "${CMAKE_COMMAND}" -E remove_directory
|
||||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
|
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources
|
||||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
)
|
||||||
"${PROJECT_SOURCE_DIR}/resources/web"
|
# copy all files in resources, including web
|
||||||
$<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"
|
||||||
|
$<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 the shared hifi libraries
|
||||||
link_hifi_libraries(embedded-webserver networking shared)
|
link_hifi_libraries(embedded-webserver networking shared)
|
||||||
|
|
128
domain-server/resources/describe-settings.json
Normal file
128
domain-server/resources/describe-settings.json
Normal 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
|
@ -1,8 +1,12 @@
|
||||||
#nodes-lead, #settings-lead {
|
body {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-lead {
|
||||||
color: #66CCCC;
|
color: #66CCCC;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nodes-lead .lead-line, #settings-lead .lead-line {
|
.table-lead .lead-line {
|
||||||
background-color: #66CCCC;
|
background-color: #66CCCC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,4 +50,23 @@ span.port {
|
||||||
|
|
||||||
.stale {
|
.stale {
|
||||||
color: red;
|
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;
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
</div>
|
</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/bootstrap.min.js'></script>
|
||||||
<script src='/js/domain-server.js'></script>
|
<script src='/js/domain-server.js'></script>
|
|
@ -25,16 +25,15 @@
|
||||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li><a href="/">Nodes</a></li>
|
<li><a href="/">Nodes</a></li>
|
||||||
<li><a href="/settings/">Settings</a></li>
|
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assignments <span class="caret"></span></a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assignments <span class="caret"></span></a>
|
||||||
<ul class="dropdown-menu" role="menu">
|
<ul class="dropdown-menu" role="menu">
|
||||||
<li><a href="/assignment">New Assignment</a></li>
|
<li><a href="/assignment">New Assignment</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
<li><a href="/settings/">Settings</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- /.container-fluid -->
|
</div><!-- /.container-fluid -->
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container">
|
<div class="container-fluid">
|
|
@ -1,63 +1,78 @@
|
||||||
<!--#include file="header.html"-->
|
<!--#include file="header.html"-->
|
||||||
<div id="nodes-lead" class="table-lead"><h3>Nodes</h3><div class="lead-line"></div></div>
|
<div class="col-md-10 col-md-offset-1">
|
||||||
<div style="clear:both;"></div>
|
<div class="panel panel-default">
|
||||||
<button type="button" class="btn btn-danger" id="kill-all-btn">
|
<div class="panel-heading">
|
||||||
<span class="glyphicon glyphicon-fire"></span> Kill all Nodes
|
<h3 class="panel-title">Nodes</h3>
|
||||||
</button>
|
</div>
|
||||||
<table id="nodes-table" class="table table-striped">
|
<div class="panel-body">
|
||||||
<thead>
|
<table id="nodes-table" class="table table-striped">
|
||||||
<tr>
|
<thead>
|
||||||
<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){ %>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><%- node.type %></td>
|
<th>Type</th>
|
||||||
<td><a href="stats/?uuid=<%- node.uuid %>"><%- node.uuid %></a></td>
|
<th>UUID</th>
|
||||||
<td><%- node.pool %></td>
|
<th>Pool</th>
|
||||||
<td><%- node.username %></td>
|
<th>Username</th>
|
||||||
<td><%- node.public.ip %><span class='port'>:<%- node.public.port %></span></td>
|
<th>Public</th>
|
||||||
<td><%- node.local.ip %><span class='port'>:<%- node.local.port %></span></td>
|
<th>Local</th>
|
||||||
<td><%- ((Date.now() - node.wake_timestamp) / 1000).toLocaleString() %></td>
|
<th>Uptime (s)</th>
|
||||||
<td><%- (typeof node.pending_credits == 'number' ? node.pending_credits.toLocaleString() : 'N/A') %></td>
|
<th>Pending Credits</th>
|
||||||
<td><span class='glyphicon glyphicon-remove' data-uuid="<%- node.uuid %>"></span></td>
|
<th>Kill?</th>
|
||||||
</tr>
|
</tr>
|
||||||
<% }); %>
|
</thead>
|
||||||
</script>
|
<tbody>
|
||||||
</table>
|
<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"-->
|
<!--#include file="footer.html"-->
|
||||||
|
<script src='js/underscore-min.js'></script>
|
||||||
<script src='js/tables.js'></script>
|
<script src='js/tables.js'></script>
|
||||||
<script src='js/underscore-1.5.0.min.js'></script>
|
|
||||||
<!--#include file="page-end.html"-->
|
<!--#include file="page-end.html"-->
|
12
domain-server/resources/web/js/bootstrap.min.js
vendored
12
domain-server/resources/web/js/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
domain-server/resources/web/js/jquery-2.1.1.min.js
vendored
Normal file
4
domain-server/resources/web/js/jquery-2.1.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -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(){
|
$(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() {
|
function reloadSettings() {
|
||||||
$.getJSON('/settings.json', function(data){
|
$.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!";
|
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
|
// disable any inputs not changed
|
||||||
$("input:not([data-changed])").each(function(){
|
$("input:not([data-changed])").each(function(){
|
||||||
$(this).prop('disabled', true);
|
$(this).prop('disabled', true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// grab a JSON representation of the form via form2js
|
// 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
|
// re-enable all inputs
|
||||||
$("input").each(function(){
|
$("input").each(function(){
|
||||||
$(this).prop('disabled', false);
|
$(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
|
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
|
||||||
$.ajax('/settings.json', {
|
$.ajax('/settings.json', {
|
||||||
data: JSON.stringify(formJSON),
|
data: JSON.stringify(formJSON),
|
||||||
|
@ -36,12 +130,11 @@ $('#settings').on('click', 'button', function(e){
|
||||||
type: 'POST'
|
type: 'POST'
|
||||||
}).done(function(data){
|
}).done(function(data){
|
||||||
if (data.status == "success") {
|
if (data.status == "success") {
|
||||||
showAlertMessage("Domain settings saved.", true);
|
showRestartModal();
|
||||||
} else {
|
} else {
|
||||||
showAlertMessage(SETTINGS_ERROR_MESSAGE, false);
|
showAlertMessage(SETTINGS_ERROR_MESSAGE, false);
|
||||||
|
reloadSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadSettings();
|
|
||||||
}).fail(function(){
|
}).fail(function(){
|
||||||
showAlertMessage(SETTINGS_ERROR_MESSAGE, false);
|
showAlertMessage(SETTINGS_ERROR_MESSAGE, false);
|
||||||
reloadSettings();
|
reloadSettings();
|
||||||
|
@ -50,10 +143,54 @@ $('#settings').on('click', 'button', function(e){
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#settings').on('change', 'input', function(){
|
function badgeSidebarForDifferences(changedInput) {
|
||||||
// this input was changed, add the changed data attribute to it
|
// figure out which group this input is in
|
||||||
$(this).attr('data-changed', true);
|
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) {
|
function cleanupFormValues(node) {
|
||||||
if (node.type && node.type === 'checkbox') {
|
if (node.type && node.type === 'checkbox') {
|
||||||
|
|
33
domain-server/resources/web/js/setup.js
Normal file
33
domain-server/resources/web/js/setup.js
Normal 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
6
domain-server/resources/web/js/underscore-min.js
vendored
Normal file
6
domain-server/resources/web/js/underscore-min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +1,78 @@
|
||||||
<!--#include virtual="header.html"-->
|
<!--#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="col-md-10 col-md-offset-1">
|
||||||
<div class="alert" style="display:none;"></div>
|
<div class="col-md-12">
|
||||||
<form class="form-horizontal" id="settings-form" role="form">
|
<div class="alert" style="display:none;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script id="settings-template" type="text/template">
|
<div class="col-md-3 col-sm-3" id="setup-sidebar-col">
|
||||||
<% _.each(descriptions, function(group, group_key){ %>
|
<div id="setup-sidebar" data-clampedwidth="#setup-sidebar-col" class="hidden-xs" data-spy="affix" data-offset-top="55">
|
||||||
<div class="panel panel-default">
|
<script id="list-group-template" type="text/template">
|
||||||
<div class="panel-heading">
|
<% _.each(descriptions, function(group){ %>
|
||||||
<h3 class="panel-title"><%- group.label %></h3>
|
<li>
|
||||||
</div>
|
<a href="#<%-group.name %>" class="list-group-item">
|
||||||
<div class="panel-body">
|
<span class="badge"></span>
|
||||||
<% _.each(group.settings, function(setting, setting_key){ %>
|
<%- group.label %>
|
||||||
<div class="form-group">
|
</a>
|
||||||
<% var setting_id = group_key + "." + setting_key %>
|
</li>
|
||||||
<label for="<%- setting_id %>" class="col-sm-2 control-label"><%- setting.label %></label>
|
<% }); %>
|
||||||
<div class="col-sm-10">
|
</script>
|
||||||
<% if (setting.type === "checkbox") { %>
|
|
||||||
<% var checked_box = _.has(values, group_key) ? values[group_key][setting_key] : setting.default %>
|
<ul class="nav nav-pills nav-stacked">
|
||||||
<input type="checkbox" id="<%- setting_id %>" <%- checked_box ? "checked" : "" %>>
|
</ul>
|
||||||
<% } else { %>
|
|
||||||
<% if (setting.input_addon) { %>
|
<button id="advanced-toggle-button" class="btn btn-info">Show advanced</button>
|
||||||
<div class="input-group">
|
<button class="btn btn-success save-button">Save and restart</button>
|
||||||
<div class="input-group-addon"><%- setting.input_addon %></div>
|
</div>
|
||||||
<% } %>
|
</div>
|
||||||
<input type="text" class="form-control" id="<%- setting_id %>"
|
|
||||||
placeholder="<%- setting.placeholder %>"
|
<div class="col-md-9 col-sm-9 col-xs-12">
|
||||||
value="<%- (values[group_key] || {})[setting_key] %>">
|
<form id="settings-form" role="form">
|
||||||
|
|
||||||
<% if (setting.input_addon) { %>
|
<script id="panels-template" type="text/template">
|
||||||
</div>
|
<% _.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>
|
</div>
|
||||||
<p class="help-block col-sm-offset-2 col-sm-10"><%- setting.help %></p>
|
|
||||||
</div>
|
</div>
|
||||||
<% }); %>
|
<% }); %>
|
||||||
</div>
|
</script>
|
||||||
</div>
|
|
||||||
<% }); %>
|
<div id="panels"></div>
|
||||||
<button type="submit" class="btn btn-default">Save</button>
|
|
||||||
</script>
|
</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"-->
|
<!--#include virtual="footer.html"-->
|
||||||
|
<script src='/js/underscore-min.js'></script>
|
||||||
<script src='/js/settings.js'></script>
|
<script src='/js/settings.js'></script>
|
||||||
<script src='/js/form2js.min.js'></script>
|
<script src='/js/form2js.min.js'></script>
|
||||||
<script src='/js/underscore-1.5.0.min.js'></script>
|
|
||||||
<!--#include virtual="page-end.html"-->
|
<!--#include virtual="page-end.html"-->
|
|
@ -30,6 +30,8 @@
|
||||||
|
|
||||||
#include "DomainServer.h"
|
#include "DomainServer.h"
|
||||||
|
|
||||||
|
int const DomainServer::EXIT_CODE_REBOOT = 234923;
|
||||||
|
|
||||||
DomainServer::DomainServer(int argc, char* argv[]) :
|
DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
QCoreApplication(argc, argv),
|
QCoreApplication(argc, argv),
|
||||||
_shutdownEventListener(this),
|
_shutdownEventListener(this),
|
||||||
|
@ -48,22 +50,21 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
_cookieSessionHash(),
|
_cookieSessionHash(),
|
||||||
_settingsManager()
|
_settingsManager()
|
||||||
{
|
{
|
||||||
|
|
||||||
LogUtils::init();
|
LogUtils::init();
|
||||||
|
|
||||||
setOrganizationName("High Fidelity");
|
setOrganizationName("High Fidelity");
|
||||||
setOrganizationDomain("highfidelity.io");
|
setOrganizationDomain("highfidelity.io");
|
||||||
setApplicationName("domain-server");
|
setApplicationName("domain-server");
|
||||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||||
|
|
||||||
|
_settingsManager.loadSettingsMap(arguments());
|
||||||
|
|
||||||
installNativeEventFilter(&_shutdownEventListener);
|
installNativeEventFilter(&_shutdownEventListener);
|
||||||
connect(&_shutdownEventListener, SIGNAL(receivedCloseEvent()), SLOT(quit()));
|
connect(&_shutdownEventListener, SIGNAL(receivedCloseEvent()), SLOT(quit()));
|
||||||
|
|
||||||
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
|
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
|
||||||
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
|
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
|
||||||
|
|
||||||
_argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
|
|
||||||
|
|
||||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
|
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
|
||||||
// we either read a certificate and private key or were not passed one
|
// we either read a certificate and private key or were not passed one
|
||||||
// and completed login or did not need to
|
// 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() {
|
bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
||||||
const QString X509_CERTIFICATE_OPTION = "cert";
|
const QString X509_CERTIFICATE_OPTION = "cert";
|
||||||
const QString X509_PRIVATE_KEY_OPTION = "key";
|
const QString X509_PRIVATE_KEY_OPTION = "key";
|
||||||
const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE";
|
const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE";
|
||||||
|
|
||||||
QString certPath = _argumentVariantMap.value(X509_CERTIFICATE_OPTION).toString();
|
QString certPath = _settingsManager.getSettingsMap().value(X509_CERTIFICATE_OPTION).toString();
|
||||||
QString keyPath = _argumentVariantMap.value(X509_PRIVATE_KEY_OPTION).toString();
|
QString keyPath = _settingsManager.getSettingsMap().value(X509_PRIVATE_KEY_OPTION).toString();
|
||||||
|
|
||||||
if (!certPath.isEmpty() && !keyPath.isEmpty()) {
|
if (!certPath.isEmpty() && !keyPath.isEmpty()) {
|
||||||
// the user wants to use DTLS to encrypt communication with nodes
|
// 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 OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET";
|
||||||
const QString REDIRECT_HOSTNAME_OPTION = "hostname";
|
const QString REDIRECT_HOSTNAME_OPTION = "hostname";
|
||||||
|
|
||||||
_oauthProviderURL = QUrl(_argumentVariantMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
|
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
|
||||||
_oauthClientID = _argumentVariantMap.value(OAUTH_CLIENT_ID_OPTION).toString();
|
_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);
|
_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 (!_oauthClientID.isEmpty()) {
|
||||||
if (_oauthProviderURL.isEmpty()
|
if (_oauthProviderURL.isEmpty()
|
||||||
|
@ -171,9 +178,11 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
||||||
|
|
||||||
const QString CUSTOM_PORT_OPTION = "port";
|
const QString CUSTOM_PORT_OPTION = "port";
|
||||||
unsigned short domainServerPort = DEFAULT_DOMAIN_SERVER_PORT;
|
unsigned short domainServerPort = DEFAULT_DOMAIN_SERVER_PORT;
|
||||||
|
|
||||||
|
QVariantMap& settingsMap = _settingsManager.getSettingsMap();
|
||||||
|
|
||||||
if (_argumentVariantMap.contains(CUSTOM_PORT_OPTION)) {
|
if (settingsMap.contains(CUSTOM_PORT_OPTION)) {
|
||||||
domainServerPort = (unsigned short) _argumentVariantMap.value(CUSTOM_PORT_OPTION).toUInt();
|
domainServerPort = (unsigned short) settingsMap.value(CUSTOM_PORT_OPTION).toUInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned short domainServerDTLSPort = 0;
|
unsigned short domainServerDTLSPort = 0;
|
||||||
|
@ -183,8 +192,8 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
||||||
|
|
||||||
const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port";
|
const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port";
|
||||||
|
|
||||||
if (_argumentVariantMap.contains(CUSTOM_DTLS_PORT_OPTION)) {
|
if (settingsMap.contains(CUSTOM_DTLS_PORT_OPTION)) {
|
||||||
domainServerDTLSPort = (unsigned short) _argumentVariantMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt();
|
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
|
// 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
|
// 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::nodeAdded, this, &DomainServer::nodeAdded);
|
||||||
connect(nodeList, &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled);
|
connect(nodeList, &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled);
|
||||||
|
@ -260,9 +273,10 @@ bool DomainServer::hasOAuthProviderAndAuthInformation() {
|
||||||
|
|
||||||
bool DomainServer::optionallySetupAssignmentPayment() {
|
bool DomainServer::optionallySetupAssignmentPayment() {
|
||||||
const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
|
const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
|
||||||
|
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
|
||||||
|
|
||||||
if (_argumentVariantMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
|
if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
|
||||||
_argumentVariantMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
|
settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
|
||||||
hasOAuthProviderAndAuthInformation()) {
|
hasOAuthProviderAndAuthInformation()) {
|
||||||
|
|
||||||
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
|
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
|
||||||
|
@ -288,8 +302,10 @@ bool DomainServer::optionallySetupAssignmentPayment() {
|
||||||
void DomainServer::setupDynamicIPAddressUpdating() {
|
void DomainServer::setupDynamicIPAddressUpdating() {
|
||||||
const QString ENABLE_DYNAMIC_IP_UPDATING_OPTION = "update-ip";
|
const QString ENABLE_DYNAMIC_IP_UPDATING_OPTION = "update-ip";
|
||||||
|
|
||||||
if (_argumentVariantMap.contains(ENABLE_DYNAMIC_IP_UPDATING_OPTION) &&
|
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
|
||||||
_argumentVariantMap.value(ENABLE_DYNAMIC_IP_UPDATING_OPTION).toBool() &&
|
|
||||||
|
if (settingsMap.contains(ENABLE_DYNAMIC_IP_UPDATING_OPTION) &&
|
||||||
|
settingsMap.value(ENABLE_DYNAMIC_IP_UPDATING_OPTION).toBool() &&
|
||||||
hasOAuthProviderAndAuthInformation()) {
|
hasOAuthProviderAndAuthInformation()) {
|
||||||
|
|
||||||
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
|
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
|
// check for configs from the command line, these take precedence
|
||||||
const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)";
|
const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)";
|
||||||
QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING);
|
QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING);
|
||||||
|
|
||||||
|
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
|
||||||
|
|
||||||
// scan for assignment config keys
|
// scan for assignment config keys
|
||||||
QStringList variantMapKeys = _argumentVariantMap.keys();
|
QStringList variantMapKeys = settingsMap.keys();
|
||||||
int configIndex = variantMapKeys.indexOf(assignmentConfigRegex);
|
int configIndex = variantMapKeys.indexOf(assignmentConfigRegex);
|
||||||
|
|
||||||
while (configIndex != -1) {
|
while (configIndex != -1) {
|
||||||
|
@ -348,7 +366,7 @@ void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes)
|
||||||
Assignment::Type assignmentType = (Assignment::Type) assignmentConfigRegex.cap(1).toInt();
|
Assignment::Type assignmentType = (Assignment::Type) assignmentConfigRegex.cap(1).toInt();
|
||||||
|
|
||||||
if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) {
|
if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) {
|
||||||
QVariant mapValue = _argumentVariantMap[variantMapKeys[configIndex]];
|
QVariant mapValue = settingsMap[variantMapKeys[configIndex]];
|
||||||
QJsonArray assignmentArray;
|
QJsonArray assignmentArray;
|
||||||
|
|
||||||
if (mapValue.type() == QVariant::String) {
|
if (mapValue.type() == QVariant::String) {
|
||||||
|
@ -513,7 +531,7 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
||||||
|
|
||||||
QString connectedUsername;
|
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
|
// 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)) {
|
if (_sessionAuthenticationHash.contains(packetUUID)) {
|
||||||
connectedUsername = _sessionAuthenticationHash.take(packetUUID);
|
connectedUsername = _sessionAuthenticationHash.take(packetUUID);
|
||||||
|
@ -1388,12 +1406,15 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
||||||
const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
|
const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
|
||||||
const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
|
const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
|
||||||
const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
|
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.";
|
const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server.";
|
||||||
|
|
||||||
|
QVariantMap& settingsMap = _settingsManager.getSettingsMap();
|
||||||
|
|
||||||
if (!_oauthProviderURL.isEmpty()
|
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);
|
QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY);
|
||||||
|
|
||||||
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
|
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);
|
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."
|
qDebug() << "Config file contains web admin settings for OAuth and basic HTTP authentication."
|
||||||
<< "These cannot be combined - using OAuth for 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);
|
DomainServerWebSessionData sessionData = _cookieSessionHash.value(cookieUUID);
|
||||||
QString profileUsername = sessionData.getUsername();
|
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
|
// this is an authenticated user
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop the roles of this user and see if they are in the admin-roles array
|
// 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()) {
|
if (!adminRolesArray.isEmpty()) {
|
||||||
foreach(const QString& userRole, sessionData.getRoles()) {
|
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
|
// we don't know about this user yet, so they are not yet authenticated
|
||||||
return false;
|
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
|
// config file contains username and password combinations for basic auth
|
||||||
const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization";
|
const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization";
|
||||||
|
|
||||||
|
@ -1470,21 +1491,16 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
||||||
if (!credentialString.isEmpty()) {
|
if (!credentialString.isEmpty()) {
|
||||||
QStringList credentialList = credentialString.split(':');
|
QStringList credentialList = credentialString.split(':');
|
||||||
if (credentialList.size() == 2) {
|
if (credentialList.size() == 2) {
|
||||||
QString username = credentialList[0];
|
QString headerUsername = credentialList[0];
|
||||||
QString password = credentialList[1];
|
QString headerPassword = credentialList[1];
|
||||||
|
|
||||||
// we've pulled a username and password - now check if there is a match in our basic auth hash
|
// 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)) {
|
if (settingsUsername == headerUsername && headerPassword == settingsPassword) {
|
||||||
const QString BASIC_AUTH_USER_PASSWORD_KEY = "password";
|
return true;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1557,7 +1573,8 @@ void DomainServer::handleProfileRequestFinished() {
|
||||||
// pull the user roles from the response
|
// pull the user roles from the response
|
||||||
QJsonArray userRolesArray = profileJSON.object()["data"].toObject()["user"].toObject()["roles"].toArray();
|
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 connectableUsername;
|
||||||
QString profileUsername = profileJSON.object()["data"].toObject()["user"].toObject()["username"].toString();
|
QString profileUsername = profileJSON.object()["data"].toObject()["user"].toObject()["username"].toString();
|
||||||
|
|
|
@ -35,16 +35,17 @@
|
||||||
typedef QSharedPointer<Assignment> SharedAssignmentPointer;
|
typedef QSharedPointer<Assignment> SharedAssignmentPointer;
|
||||||
typedef QMultiHash<QUuid, WalletTransaction*> TransactionHash;
|
typedef QMultiHash<QUuid, WalletTransaction*> TransactionHash;
|
||||||
|
|
||||||
|
|
||||||
class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
DomainServer(int argc, char* argv[]);
|
DomainServer(int argc, char* argv[]);
|
||||||
|
|
||||||
|
static int const EXIT_CODE_REBOOT;
|
||||||
|
|
||||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||||
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url);
|
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url);
|
||||||
|
|
||||||
void exit(int retCode = 0);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/// Called by NodeList to inform us a node has been added
|
/// Called by NodeList to inform us a node has been added
|
||||||
void nodeAdded(SharedNodePointer node);
|
void nodeAdded(SharedNodePointer node);
|
||||||
|
@ -53,6 +54,8 @@ public slots:
|
||||||
|
|
||||||
void transactionJSONCallback(const QJsonObject& data);
|
void transactionJSONCallback(const QJsonObject& data);
|
||||||
|
|
||||||
|
void restart();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void loginFailed();
|
void loginFailed();
|
||||||
void readAvailableDatagrams();
|
void readAvailableDatagrams();
|
||||||
|
@ -115,8 +118,6 @@ private:
|
||||||
QHash<QUuid, PendingAssignedNodeData*> _pendingAssignedNodes;
|
QHash<QUuid, PendingAssignedNodeData*> _pendingAssignedNodes;
|
||||||
TransactionHash _pendingAssignmentCredits;
|
TransactionHash _pendingAssignmentCredits;
|
||||||
|
|
||||||
QVariantMap _argumentVariantMap;
|
|
||||||
|
|
||||||
bool _isUsingDTLS;
|
bool _isUsingDTLS;
|
||||||
|
|
||||||
QUrl _oauthProviderURL;
|
QUrl _oauthProviderURL;
|
||||||
|
|
|
@ -10,45 +10,44 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <QtCore/QCoreApplication>
|
#include <QtCore/QCoreApplication>
|
||||||
|
#include <QtCore/QDir>
|
||||||
#include <QtCore/QFile>
|
#include <QtCore/QFile>
|
||||||
#include <QtCore/QJsonArray>
|
#include <QtCore/QJsonArray>
|
||||||
#include <QtCore/QJsonObject>
|
#include <QtCore/QJsonObject>
|
||||||
|
#include <QtCore/QStandardPaths>
|
||||||
#include <QtCore/QUrl>
|
#include <QtCore/QUrl>
|
||||||
#include <QtCore/QUrlQuery>
|
#include <QtCore/QUrlQuery>
|
||||||
|
|
||||||
#include <Assignment.h>
|
#include <Assignment.h>
|
||||||
|
#include <HifiConfigVariantMap.h>
|
||||||
#include <HTTPConnection.h>
|
#include <HTTPConnection.h>
|
||||||
|
|
||||||
#include "DomainServerSettingsManager.h"
|
#include "DomainServerSettingsManager.h"
|
||||||
|
|
||||||
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/web/settings/describe.json";
|
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
|
||||||
const QString SETTINGS_JSON_FILE_RELATIVE_PATH = "/resources/settings.json";
|
|
||||||
|
|
||||||
DomainServerSettingsManager::DomainServerSettingsManager() :
|
DomainServerSettingsManager::DomainServerSettingsManager() :
|
||||||
_descriptionObject(),
|
_descriptionArray(),
|
||||||
_settingsMap()
|
_settingsMap()
|
||||||
{
|
{
|
||||||
// load the description object from the settings description
|
// load the description object from the settings description
|
||||||
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
|
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
|
||||||
descriptionFile.open(QIODevice::ReadOnly);
|
descriptionFile.open(QIODevice::ReadOnly);
|
||||||
|
|
||||||
_descriptionObject = QJsonDocument::fromJson(descriptionFile.readAll()).object();
|
_descriptionArray = QJsonDocument::fromJson(descriptionFile.readAll()).array();
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString DESCRIPTION_SETTINGS_KEY = "settings";
|
void DomainServerSettingsManager::loadSettingsMap(const QStringList& argumentList) {
|
||||||
const QString SETTING_DEFAULT_KEY = "default";
|
_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) {
|
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
|
// this is a GET operation for our settings
|
||||||
|
|
||||||
// check if there is a query parameter for settings affecting a particular type of assignment
|
// 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);
|
QUrlQuery settingsQuery(url);
|
||||||
QString typeValue = settingsQuery.queryItemValue(SETTINGS_TYPE_QUERY_KEY);
|
QString typeValue = settingsQuery.queryItemValue(SETTINGS_TYPE_QUERY_KEY);
|
||||||
|
|
||||||
QJsonObject responseObject;
|
if (!typeValue.isEmpty()) {
|
||||||
|
QJsonObject responseObject = responseObjectForType(typeValue);
|
||||||
if (typeValue.isEmpty()) {
|
|
||||||
// combine the description object and our current settings map
|
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(responseObject).toJson(), "application/json");
|
||||||
responseObject["descriptions"] = _descriptionObject;
|
|
||||||
responseObject["values"] = QJsonDocument::fromVariant(_settingsMap).object();
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// convert the string type value to a QJsonValue
|
return false;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(responseObject).toJson(), "application/json");
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
|
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
|
// this is a POST operation to change one or more settings
|
||||||
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
|
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
|
||||||
QJsonObject postedObject = postedDocument.object();
|
QJsonObject postedObject = postedDocument.object();
|
||||||
|
|
||||||
// we recurse one level deep below each group for the appropriate setting
|
// 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
|
// store whatever the current _settingsMap is to file
|
||||||
persistToFile();
|
persistToFile();
|
||||||
|
@ -135,56 +85,153 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
|
||||||
QString jsonSuccess = "{\"status\": \"success\"}";
|
QString jsonSuccess = "{\"status\": \"success\"}";
|
||||||
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
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;
|
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;
|
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";
|
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
|
||||||
|
|
||||||
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
|
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
|
||||||
QVariantMap& settingsVariant,
|
QVariantMap& settingsVariant,
|
||||||
QJsonObject descriptionObject) {
|
QJsonArray descriptionArray) {
|
||||||
foreach(const QString& key, postedObject.keys()) {
|
foreach(const QString& key, postedObject.keys()) {
|
||||||
|
|
||||||
QJsonValue rootValue = postedObject[key];
|
QJsonValue rootValue = postedObject[key];
|
||||||
|
|
||||||
// we don't continue if this key is not present in our descriptionObject
|
// we don't continue if this key is not present in our descriptionObject
|
||||||
if (descriptionObject.contains(key)) {
|
foreach(const QJsonValue& groupValue, descriptionArray) {
|
||||||
if (rootValue.isString()) {
|
if (groupValue.toObject()[DESCRIPTION_NAME_KEY].toString() == key) {
|
||||||
if (rootValue.toString().isEmpty()) {
|
QJsonObject groupObject = groupValue.toObject();
|
||||||
// this is an empty value, clear it in settings variant so the default is sent
|
if (rootValue.isString()) {
|
||||||
settingsVariant.remove(key);
|
if (rootValue.toString().isEmpty()) {
|
||||||
} else {
|
// this is an empty value, clear it in settings variant so the default is sent
|
||||||
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
|
|
||||||
settingsVariant.remove(key);
|
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() {
|
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)) {
|
if (settingsFile.open(QIODevice::WriteOnly)) {
|
||||||
settingsFile.write(getJSONSettingsMap());
|
settingsFile.write(getJSONSettingsMap());
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#ifndef hifi_DomainServerSettingsManager_h
|
#ifndef hifi_DomainServerSettingsManager_h
|
||||||
#define hifi_DomainServerSettingsManager_h
|
#define hifi_DomainServerSettingsManager_h
|
||||||
|
|
||||||
|
#include <QtCore/QJsonArray>
|
||||||
#include <QtCore/QJsonDocument>
|
#include <QtCore/QJsonDocument>
|
||||||
|
|
||||||
#include <HTTPManager.h>
|
#include <HTTPManager.h>
|
||||||
|
@ -23,14 +24,19 @@ public:
|
||||||
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||||
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||||
|
|
||||||
|
void loadSettingsMap(const QStringList& argumentList);
|
||||||
|
|
||||||
QByteArray getJSONSettingsMap() const;
|
QByteArray getJSONSettingsMap() const;
|
||||||
|
QVariantMap& getSettingsMap() { return _settingsMap; }
|
||||||
private:
|
private:
|
||||||
|
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
|
||||||
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant,
|
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant,
|
||||||
QJsonObject descriptionObject);
|
QJsonArray descriptionArray);
|
||||||
void persistToFile();
|
void persistToFile();
|
||||||
|
|
||||||
QJsonObject _descriptionObject;
|
QJsonArray _descriptionArray;
|
||||||
QVariantMap _settingsMap;
|
QVariantMap _settingsMap;
|
||||||
|
QString _settingsFilepath;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_DomainServerSettingsManager_h
|
#endif // hifi_DomainServerSettingsManager_h
|
|
@ -28,8 +28,16 @@ int main(int argc, char* argv[]) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
qInstallMessageHandler(Logging::verboseMessageHandler);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2907,8 +2907,6 @@ function handeMenuEvent(menuItem) {
|
||||||
index++;
|
index++;
|
||||||
array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) });
|
array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) });
|
||||||
index++;
|
index++;
|
||||||
array.push({ label: "Mass:", value: properties.mass.toFixed(decimals) });
|
|
||||||
index++;
|
|
||||||
array.push({ label: "Angular Pitch:", value: properties.angularVelocity.x.toFixed(decimals) });
|
array.push({ label: "Angular Pitch:", value: properties.angularVelocity.x.toFixed(decimals) });
|
||||||
index++;
|
index++;
|
||||||
array.push({ label: "Angular Yaw:", value: properties.angularVelocity.y.toFixed(decimals) });
|
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) });
|
array.push({ label: "Gravity Z:", value: properties.gravity.z.toFixed(decimals) });
|
||||||
index++;
|
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) });
|
array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) });
|
||||||
index++;
|
index++;
|
||||||
|
|
||||||
|
@ -3055,7 +3062,6 @@ Window.nonBlockingFormClosed.connect(function() {
|
||||||
properties.velocity.y = array[index++].value;
|
properties.velocity.y = array[index++].value;
|
||||||
properties.velocity.z = array[index++].value;
|
properties.velocity.z = array[index++].value;
|
||||||
properties.damping = array[index++].value;
|
properties.damping = array[index++].value;
|
||||||
properties.mass = array[index++].value;
|
|
||||||
|
|
||||||
properties.angularVelocity.x = array[index++].value;
|
properties.angularVelocity.x = array[index++].value;
|
||||||
properties.angularVelocity.y = 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.x = array[index++].value;
|
||||||
properties.gravity.y = array[index++].value;
|
properties.gravity.y = array[index++].value;
|
||||||
properties.gravity.z = 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.lifetime = array[index++].value;
|
||||||
properties.visible = array[index++].value;
|
properties.visible = array[index++].value;
|
||||||
|
|
||||||
|
|
18
interface/resources/images/address-bar-submit-active.svg
Normal file
18
interface/resources/images/address-bar-submit-active.svg
Normal 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 |
18
interface/resources/images/address-bar-submit.svg
Normal file
18
interface/resources/images/address-bar-submit.svg
Normal 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 |
|
@ -680,7 +680,7 @@ void Application::paintGL() {
|
||||||
if (whichCamera.getMode() == CAMERA_MODE_MIRROR) {
|
if (whichCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||||
OculusManager::display(whichCamera.getRotation(), whichCamera.getPosition(), whichCamera);
|
OculusManager::display(whichCamera.getRotation(), whichCamera.getPosition(), whichCamera);
|
||||||
} else {
|
} else {
|
||||||
OculusManager::display(_myAvatar->getWorldAlignedOrientation(), whichCamera.getPosition(), whichCamera);
|
OculusManager::display(_myAvatar->getWorldAlignedOrientation(), _myAvatar->getDefaultEyePosition(), whichCamera);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (TV3DManager::isConnected()) {
|
} else if (TV3DManager::isConnected()) {
|
||||||
|
@ -921,12 +921,11 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
||||||
|
|
||||||
case Qt::Key_Return:
|
case Qt::Key_Return:
|
||||||
case Qt::Key_Enter:
|
case Qt::Key_Enter:
|
||||||
if (isMeta) {
|
Menu::getInstance()->triggerOption(MenuOption::AddressBar);
|
||||||
Menu::getInstance()->triggerOption(MenuOption::AddressBar);
|
break;
|
||||||
} else {
|
|
||||||
Menu::getInstance()->triggerOption(MenuOption::Chat);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
case Qt::Key_Backslash:
|
||||||
|
Menu::getInstance()->triggerOption(MenuOption::Chat);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Qt::Key_N:
|
case Qt::Key_N:
|
||||||
|
|
|
@ -176,7 +176,7 @@ Menu::Menu() :
|
||||||
SLOT(toggleLocationList()));
|
SLOT(toggleLocationList()));
|
||||||
addActionToQMenuAndActionHash(fileMenu,
|
addActionToQMenuAndActionHash(fileMenu,
|
||||||
MenuOption::AddressBar,
|
MenuOption::AddressBar,
|
||||||
Qt::CTRL | Qt::Key_Enter,
|
Qt::Key_Enter,
|
||||||
this,
|
this,
|
||||||
SLOT(toggleAddressBar()));
|
SLOT(toggleAddressBar()));
|
||||||
|
|
||||||
|
@ -1156,22 +1156,13 @@ void Menu::changePrivateKey() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::toggleAddressBar() {
|
void Menu::toggleAddressBar() {
|
||||||
|
if (!_addressBarDialog) {
|
||||||
QInputDialog addressBarDialog(Application::getInstance()->getWindow());
|
_addressBarDialog = new AddressBarDialog();
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendFakeEnterEvent();
|
if (!_addressBarDialog->isVisible()) {
|
||||||
|
_addressBarDialog->show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::displayAddressOfflineMessage() {
|
void Menu::displayAddressOfflineMessage() {
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "SpeechRecognizer.h"
|
#include "SpeechRecognizer.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "ui/AddressBarDialog.h"
|
||||||
#include "ui/ChatWindow.h"
|
#include "ui/ChatWindow.h"
|
||||||
#include "ui/DataWebDialog.h"
|
#include "ui/DataWebDialog.h"
|
||||||
#include "ui/JSConsole.h"
|
#include "ui/JSConsole.h"
|
||||||
|
@ -298,6 +299,7 @@ private:
|
||||||
QPointer<PreferencesDialog> _preferencesDialog;
|
QPointer<PreferencesDialog> _preferencesDialog;
|
||||||
QPointer<AttachmentsDialog> _attachmentsDialog;
|
QPointer<AttachmentsDialog> _attachmentsDialog;
|
||||||
QPointer<AnimationsDialog> _animationsDialog;
|
QPointer<AnimationsDialog> _animationsDialog;
|
||||||
|
QPointer<AddressBarDialog> _addressBarDialog;
|
||||||
QPointer<LoginDialog> _loginDialog;
|
QPointer<LoginDialog> _loginDialog;
|
||||||
bool _hasLoginDialogDisplayed;
|
bool _hasLoginDialogDisplayed;
|
||||||
QAction* _chatAction;
|
QAction* _chatAction;
|
||||||
|
|
|
@ -505,10 +505,11 @@ void Avatar::renderBody(RenderMode renderMode, bool postLighting, float glowLeve
|
||||||
{
|
{
|
||||||
Glower glower(glowLevel);
|
Glower glower(glowLevel);
|
||||||
|
|
||||||
if ((_shouldRenderBillboard || !(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) &&
|
if (_shouldRenderBillboard || !(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) {
|
||||||
(postLighting || renderMode == SHADOW_RENDER_MODE)) {
|
if (postLighting || renderMode == SHADOW_RENDER_MODE) {
|
||||||
// render the billboard until both models are loaded
|
// render the billboard until both models are loaded
|
||||||
renderBillboard();
|
renderBillboard();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1010,6 +1010,10 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const {
|
||||||
return _position + getWorldAlignedOrientation() * glm::vec3(0.0f, getPelvisToHeadLength(), 0.0f);
|
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 SCRIPT_PRIORITY = DEFAULT_PRIORITY + 1.0f;
|
||||||
const float RECORDER_PRIORITY = SCRIPT_PRIORITY + 1.0f;
|
const float RECORDER_PRIORITY = SCRIPT_PRIORITY + 1.0f;
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,7 @@ public:
|
||||||
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
|
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
|
||||||
glm::vec3 getGravity() const { return _gravity; }
|
glm::vec3 getGravity() const { return _gravity; }
|
||||||
glm::vec3 getUprightHeadPosition() const;
|
glm::vec3 getUprightHeadPosition() const;
|
||||||
|
glm::vec3 getDefaultEyePosition() const;
|
||||||
bool getShouldRenderLocally() const { return _shouldRender; }
|
bool getShouldRenderLocally() const { return _shouldRender; }
|
||||||
|
|
||||||
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _animationHandles; }
|
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _animationHandles; }
|
||||||
|
|
|
@ -27,7 +27,8 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
|
||||||
_owningAvatar(owningAvatar),
|
_owningAvatar(owningAvatar),
|
||||||
_boundingShape(),
|
_boundingShape(),
|
||||||
_boundingShapeLocalOffset(0.0f),
|
_boundingShapeLocalOffset(0.0f),
|
||||||
_ragdoll(NULL) {
|
_ragdoll(NULL),
|
||||||
|
_defaultEyeModelPosition(glm::vec3(0.f, 0.f, 0.f)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SkeletonModel::~SkeletonModel() {
|
SkeletonModel::~SkeletonModel() {
|
||||||
|
@ -470,23 +471,23 @@ bool SkeletonModel::getNeckParentRotationFromDefaultOrientation(glm::quat& neckP
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
|
bool SkeletonModel::getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
|
||||||
if (!isActive()) {
|
if (!isActive()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
if (getJointPositionInWorldFrame(geometry.leftEyeJointIndex, firstEyePosition) &&
|
if (getJointPosition(geometry.leftEyeJointIndex, firstEyePosition) &&
|
||||||
getJointPositionInWorldFrame(geometry.rightEyeJointIndex, secondEyePosition)) {
|
getJointPosition(geometry.rightEyeJointIndex, secondEyePosition)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// no eye joints; try to estimate based on head/neck joints
|
// no eye joints; try to estimate based on head/neck joints
|
||||||
glm::vec3 neckPosition, headPosition;
|
glm::vec3 neckPosition, headPosition;
|
||||||
if (getJointPositionInWorldFrame(geometry.neckJointIndex, neckPosition) &&
|
if (getJointPosition(geometry.neckJointIndex, neckPosition) &&
|
||||||
getJointPositionInWorldFrame(geometry.headJointIndex, headPosition)) {
|
getJointPosition(geometry.headJointIndex, headPosition)) {
|
||||||
const float EYE_PROPORTION = 0.6f;
|
const float EYE_PROPORTION = 0.6f;
|
||||||
glm::vec3 baseEyePosition = glm::mix(neckPosition, headPosition, EYE_PROPORTION);
|
glm::vec3 baseEyePosition = glm::mix(neckPosition, headPosition, EYE_PROPORTION);
|
||||||
glm::quat headRotation;
|
glm::quat headRotation;
|
||||||
getJointRotationInWorldFrame(geometry.headJointIndex, headRotation);
|
getJointRotation(geometry.headJointIndex, headRotation);
|
||||||
const float EYES_FORWARD = 0.25f;
|
const float EYES_FORWARD = 0.25f;
|
||||||
const float EYE_SEPARATION = 0.1f;
|
const float EYE_SEPARATION = 0.1f;
|
||||||
float headHeight = glm::distance(neckPosition, headPosition);
|
float headHeight = glm::distance(neckPosition, headPosition);
|
||||||
|
@ -497,6 +498,15 @@ bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& seco
|
||||||
return false;
|
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() {
|
void SkeletonModel::renderRagdoll() {
|
||||||
if (!_ragdoll) {
|
if (!_ragdoll) {
|
||||||
return;
|
return;
|
||||||
|
@ -656,6 +666,20 @@ void SkeletonModel::buildShapes() {
|
||||||
// This method moves the shapes to their default positions in Model frame.
|
// This method moves the shapes to their default positions in Model frame.
|
||||||
computeBoundingShape(geometry);
|
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
|
// While the shapes are in their default position we disable collisions between
|
||||||
// joints that are currently colliding.
|
// joints that are currently colliding.
|
||||||
disableCurrentSelfCollisions();
|
disableCurrentSelfCollisions();
|
||||||
|
|
|
@ -97,6 +97,10 @@ public:
|
||||||
/// \return whether or not both eye meshes were found
|
/// \return whether or not both eye meshes were found
|
||||||
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
|
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();
|
virtual void updateVisibleJointStates();
|
||||||
|
|
||||||
SkeletonRagdoll* buildRagdoll();
|
SkeletonRagdoll* buildRagdoll();
|
||||||
|
@ -140,12 +144,16 @@ private:
|
||||||
/// \param position position of joint in model-frame
|
/// \param position position of joint in model-frame
|
||||||
/// \param rotation rotation 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);
|
void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation);
|
||||||
|
|
||||||
|
bool getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
|
||||||
|
|
||||||
Avatar* _owningAvatar;
|
Avatar* _owningAvatar;
|
||||||
|
|
||||||
CapsuleShape _boundingShape;
|
CapsuleShape _boundingShape;
|
||||||
glm::vec3 _boundingShapeLocalOffset;
|
glm::vec3 _boundingShapeLocalOffset;
|
||||||
SkeletonRagdoll* _ragdoll;
|
SkeletonRagdoll* _ragdoll;
|
||||||
|
|
||||||
|
glm::vec3 _defaultEyeModelPosition;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_SkeletonModel_h
|
#endif // hifi_SkeletonModel_h
|
||||||
|
|
|
@ -602,6 +602,14 @@ bool Model::getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) co
|
||||||
return true;
|
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 {
|
bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const {
|
||||||
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
|
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -148,6 +148,11 @@ public:
|
||||||
/// \return true if joint exists
|
/// \return true if joint exists
|
||||||
bool getJointPosition(int jointIndex, glm::vec3& position) const;
|
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;
|
QStringList getJointNames() const;
|
||||||
|
|
||||||
AnimationHandlePointer createAnimationHandle();
|
AnimationHandlePointer createAnimationHandle();
|
||||||
|
|
131
interface/src/ui/AddressBarDialog.cpp
Normal file
131
interface/src/ui/AddressBarDialog.cpp
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
46
interface/src/ui/AddressBarDialog.h
Normal file
46
interface/src/ui/AddressBarDialog.h
Normal 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
|
|
@ -78,6 +78,11 @@ void EntityCollisionSystem::emitGlobalEntityCollisionWithEntity(EntityItem* enti
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityCollisionSystem::updateCollisionWithVoxels(EntityItem* entity) {
|
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);
|
glm::vec3 center = entity->getPosition() * (float)(TREE_SCALE);
|
||||||
float radius = entity->getRadius() * (float)(TREE_SCALE);
|
float radius = entity->getRadius() * (float)(TREE_SCALE);
|
||||||
const float ELASTICITY = 0.4f;
|
const float ELASTICITY = 0.4f;
|
||||||
|
@ -105,82 +110,110 @@ void EntityCollisionSystem::updateCollisionWithVoxels(EntityItem* entity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
|
void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
|
||||||
|
|
||||||
|
if (entityA->getIgnoreForCollisions()) {
|
||||||
|
return; // bail early if this entity is to be ignored...
|
||||||
|
}
|
||||||
|
|
||||||
glm::vec3 penetration;
|
glm::vec3 penetration;
|
||||||
EntityItem* entityB = NULL;
|
EntityItem* entityB = NULL;
|
||||||
|
|
||||||
const float MAX_COLLISIONS_PER_ENTITY = 32;
|
const int MAX_COLLISIONS_PER_ENTITY = 32;
|
||||||
CollisionList collisions(MAX_COLLISIONS_PER_ENTITY);
|
CollisionList collisions(MAX_COLLISIONS_PER_ENTITY);
|
||||||
bool shapeCollisionsAccurate = false;
|
bool shapeCollisionsAccurate = false;
|
||||||
|
|
||||||
bool shapeCollisions = _entities->findShapeCollisions(&entityA->getCollisionShapeInMeters(),
|
bool shapeCollisions = _entities->findShapeCollisions(&entityA->getCollisionShapeInMeters(),
|
||||||
collisions, Octree::NoLock, &shapeCollisionsAccurate);
|
collisions, Octree::NoLock, &shapeCollisionsAccurate);
|
||||||
|
|
||||||
|
|
||||||
if (shapeCollisions) {
|
if (shapeCollisions) {
|
||||||
for(int i = 0; i < collisions.size(); i++) {
|
for(int i = 0; i < collisions.size(); i++) {
|
||||||
|
|
||||||
CollisionInfo* collision = collisions[i];
|
CollisionInfo* collision = collisions[i];
|
||||||
penetration = collision->_penetration;
|
penetration = collision->_penetration;
|
||||||
entityB = static_cast<EntityItem*>(collision->_extraData);
|
entityB = static_cast<EntityItem*>(collision->_extraData);
|
||||||
|
|
||||||
// TODO: how to handle multiple collisions?
|
// NOTE: 'penetration' is the depth that 'entityA' overlaps 'entityB'. It points from A into B.
|
||||||
break;
|
glm::vec3 penetrationInTreeUnits = penetration / (float)(TREE_SCALE);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shapeCollisions) {
|
|
||||||
// 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
|
// Even if the Entities overlap... when the Entities are already moving appart
|
||||||
// we don't want to count this as a collision.
|
// we don't want to count this as a collision.
|
||||||
glm::vec3 relativeVelocity = entityA->getVelocity() - entityB->getVelocity();
|
glm::vec3 relativeVelocity = entityA->getVelocity() - entityB->getVelocity();
|
||||||
|
|
||||||
|
bool fullyEnclosedCollision = glm::length(penetrationInTreeUnits) > entityA->getLargestDimension();
|
||||||
|
|
||||||
bool movingTowardEachOther = glm::dot(relativeVelocity, penetrationInTreeUnits) > 0.0f;
|
bool wantToMoveA = entityA->getCollisionsWillMove();
|
||||||
bool doCollisions = movingTowardEachOther; // don't do collisions if the entities are moving away from each other
|
bool wantToMoveB = entityB->getCollisionsWillMove();
|
||||||
|
bool movingTowardEachOther = glm::dot(relativeVelocity, penetrationInTreeUnits) > 0.0f;
|
||||||
|
|
||||||
if (doCollisions) {
|
// only do collisions if the entities are moving toward each other and one or the other
|
||||||
quint64 now = usecTimestampNow();
|
// of the entities are movable from collisions
|
||||||
|
bool doCollisions = !fullyEnclosedCollision && movingTowardEachOther && (wantToMoveA || wantToMoveB);
|
||||||
|
|
||||||
|
if (doCollisions) {
|
||||||
|
|
||||||
|
quint64 now = usecTimestampNow();
|
||||||
|
|
||||||
CollisionInfo collision;
|
CollisionInfo collision;
|
||||||
collision._penetration = penetration;
|
collision._penetration = penetration;
|
||||||
// for now the contactPoint is the average between the the two paricle centers
|
// for now the contactPoint is the average between the the two paricle centers
|
||||||
collision._contactPoint = (0.5f * (float)TREE_SCALE) * (entityA->getPosition() + entityB->getPosition());
|
collision._contactPoint = (0.5f * (float)TREE_SCALE) * (entityA->getPosition() + entityB->getPosition());
|
||||||
emitGlobalEntityCollisionWithEntity(entityA, entityB, collision);
|
emitGlobalEntityCollisionWithEntity(entityA, entityB, collision);
|
||||||
|
|
||||||
glm::vec3 axis = glm::normalize(penetration);
|
glm::vec3 axis = glm::normalize(penetration);
|
||||||
glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis;
|
glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis;
|
||||||
|
|
||||||
float massA = entityA->getMass();
|
float massA = entityA->getMass();
|
||||||
float massB = entityB->getMass();
|
float massB = entityB->getMass();
|
||||||
float totalMass = massA + massB;
|
float totalMass = massA + massB;
|
||||||
|
float massRatioA = (2.0f * massB / totalMass);
|
||||||
|
float massRatioB = (2.0f * massA / totalMass);
|
||||||
|
|
||||||
// handle Entity A
|
// in the event that one of our entities is non-moving, then fix up these ratios
|
||||||
glm::vec3 newVelocityA = entityA->getVelocity() - axialVelocity * (2.0f * massB / totalMass);
|
if (wantToMoveA && !wantToMoveB) {
|
||||||
glm::vec3 newPositionA = entityA->getPosition() - 0.5f * penetrationInTreeUnits;
|
massRatioA = 2.0f;
|
||||||
|
massRatioB = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
EntityItemProperties propertiesA = entityA->getProperties();
|
if (!wantToMoveA && wantToMoveB) {
|
||||||
EntityItemID idA(entityA->getID());
|
massRatioA = 0.0f;
|
||||||
propertiesA.setVelocity(newVelocityA * (float)TREE_SCALE);
|
massRatioB = 2.0f;
|
||||||
propertiesA.setPosition(newPositionA * (float)TREE_SCALE);
|
}
|
||||||
propertiesA.setLastEdited(now);
|
|
||||||
|
|
||||||
_entities->updateEntity(idA, propertiesA);
|
// unless the entity is configured to not be moved by collision, calculate it's new position
|
||||||
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idA, propertiesA);
|
// 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);
|
EntityItemProperties propertiesA = entityA->getProperties();
|
||||||
glm::vec3 newPositionB = entityB->getPosition() + 0.5f * penetrationInTreeUnits;
|
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());
|
// unless the entity is configured to not be moved by collision, calculate it's new position
|
||||||
propertiesB.setVelocity(newVelocityB * (float)TREE_SCALE);
|
// and velocity and apply it
|
||||||
propertiesB.setPosition(newPositionB * (float)TREE_SCALE);
|
if (wantToMoveB) {
|
||||||
propertiesB.setLastEdited(now);
|
glm::vec3 newVelocityB = entityB->getVelocity() + axialVelocity * massRatioB;
|
||||||
|
glm::vec3 newPositionB = entityB->getPosition() + 0.5f * penetrationInTreeUnits;
|
||||||
|
|
||||||
_entities->updateEntity(idB, propertiesB);
|
EntityItemProperties propertiesB = entityB->getProperties();
|
||||||
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idB, propertiesB);
|
|
||||||
|
EntityItemID idB(entityB->getID());
|
||||||
// TODO: Do we need this?
|
propertiesB.setVelocity(newVelocityB * (float)TREE_SCALE);
|
||||||
//_packetSender->releaseQueuedMessages();
|
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;
|
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);
|
glm::vec3 center = entity->getPosition() * (float)(TREE_SCALE);
|
||||||
float radius = entity->getRadius() * (float)(TREE_SCALE);
|
float radius = entity->getRadius() * (float)(TREE_SCALE);
|
||||||
const float ELASTICITY = 0.9f;
|
const float ELASTICITY = 0.9f;
|
||||||
|
|
|
@ -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 glm::vec3 EntityItem::DEFAULT_ANGULAR_VELOCITY = NO_ANGULAR_VELOCITY;
|
||||||
const float EntityItem::DEFAULT_ANGULAR_DAMPING = 0.5f;
|
const float EntityItem::DEFAULT_ANGULAR_DAMPING = 0.5f;
|
||||||
const bool EntityItem::DEFAULT_VISIBLE = true;
|
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) {
|
void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
|
||||||
_id = entityItemID.id;
|
_id = entityItemID.id;
|
||||||
|
@ -70,6 +72,8 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
|
||||||
_angularVelocity = DEFAULT_ANGULAR_VELOCITY;
|
_angularVelocity = DEFAULT_ANGULAR_VELOCITY;
|
||||||
_angularDamping = DEFAULT_ANGULAR_DAMPING;
|
_angularDamping = DEFAULT_ANGULAR_DAMPING;
|
||||||
_visible = DEFAULT_VISIBLE;
|
_visible = DEFAULT_VISIBLE;
|
||||||
|
_ignoreForCollisions = DEFAULT_IGNORE_FOR_COLLISIONS;
|
||||||
|
_collisionsWillMove = DEFAULT_COLLISIONS_WILL_MOVE;
|
||||||
|
|
||||||
recalculateCollisionShape();
|
recalculateCollisionShape();
|
||||||
}
|
}
|
||||||
|
@ -111,6 +115,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
||||||
requestedProperties += PROP_ANGULAR_VELOCITY;
|
requestedProperties += PROP_ANGULAR_VELOCITY;
|
||||||
requestedProperties += PROP_ANGULAR_DAMPING;
|
requestedProperties += PROP_ANGULAR_DAMPING;
|
||||||
requestedProperties += PROP_VISIBLE;
|
requestedProperties += PROP_VISIBLE;
|
||||||
|
requestedProperties += PROP_IGNORE_FOR_COLLISIONS;
|
||||||
|
requestedProperties += PROP_COLLISIONS_WILL_MOVE;
|
||||||
|
|
||||||
return requestedProperties;
|
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_VELOCITY, appendValue, getAngularVelocity());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, appendValue, getAngularDamping());
|
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, appendValue, getAngularDamping());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_VISIBLE, appendValue, getVisible());
|
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,
|
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
|
||||||
requestedProperties,
|
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_VELOCITY, glm::vec3, _angularVelocity);
|
||||||
READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, _angularDamping);
|
READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, _angularDamping);
|
||||||
READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, _visible);
|
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) {
|
if (wantDebug) {
|
||||||
qDebug() << " readEntityDataFromBuffer() _registrationPoint:" << _registrationPoint;
|
qDebug() << " readEntityDataFromBuffer() _registrationPoint:" << _registrationPoint;
|
||||||
qDebug() << " readEntityDataFromBuffer() _visible:" << _visible;
|
qDebug() << " readEntityDataFromBuffer() _visible:" << _visible;
|
||||||
|
qDebug() << " readEntityDataFromBuffer() _ignoreForCollisions:" << _ignoreForCollisions;
|
||||||
|
qDebug() << " readEntityDataFromBuffer() _collisionsWillMove:" << _collisionsWillMove;
|
||||||
}
|
}
|
||||||
|
|
||||||
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData);
|
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(angularDamping, getAngularDamping);
|
||||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel);
|
COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel);
|
||||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible);
|
COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible);
|
||||||
|
COPY_ENTITY_PROPERTY_TO_PROPERTIES(ignoreForCollisions, getIgnoreForCollisions);
|
||||||
|
COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionsWillMove, getCollisionsWillMove);
|
||||||
|
|
||||||
properties._defaultSettings = false;
|
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(angularDamping, setAngularDamping);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible);
|
||||||
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(ignoreForCollisions, setIgnoreForCollisions);
|
||||||
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, setCollisionsWillMove);
|
||||||
|
|
||||||
if (somethingChanged) {
|
if (somethingChanged) {
|
||||||
somethingChangedNotification(); // notify derived classes that something has changed
|
somethingChangedNotification(); // notify derived classes that something has changed
|
||||||
|
|
|
@ -230,6 +230,14 @@ public:
|
||||||
void setVisible(bool value) { _visible = value; }
|
void setVisible(bool value) { _visible = value; }
|
||||||
bool isVisible() const { return _visible; }
|
bool isVisible() const { return _visible; }
|
||||||
bool isInvisible() 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()...
|
// TODO: We need to get rid of these users of getRadius()...
|
||||||
float getRadius() const;
|
float getRadius() const;
|
||||||
|
@ -265,6 +273,8 @@ protected:
|
||||||
glm::vec3 _angularVelocity;
|
glm::vec3 _angularVelocity;
|
||||||
float _angularDamping;
|
float _angularDamping;
|
||||||
bool _visible;
|
bool _visible;
|
||||||
|
bool _ignoreForCollisions;
|
||||||
|
bool _collisionsWillMove;
|
||||||
|
|
||||||
// NOTE: Radius support is obsolete, but these private helper functions are available for this class to
|
// NOTE: Radius support is obsolete, but these private helper functions are available for this class to
|
||||||
// parse old data streams
|
// parse old data streams
|
||||||
|
|
|
@ -41,6 +41,8 @@ EntityItemProperties::EntityItemProperties() :
|
||||||
_angularVelocity(EntityItem::DEFAULT_ANGULAR_VELOCITY),
|
_angularVelocity(EntityItem::DEFAULT_ANGULAR_VELOCITY),
|
||||||
_angularDamping(EntityItem::DEFAULT_ANGULAR_DAMPING),
|
_angularDamping(EntityItem::DEFAULT_ANGULAR_DAMPING),
|
||||||
_visible(EntityItem::DEFAULT_VISIBLE),
|
_visible(EntityItem::DEFAULT_VISIBLE),
|
||||||
|
_ignoreForCollisions(EntityItem::DEFAULT_IGNORE_FOR_COLLISIONS),
|
||||||
|
_collisionsWillMove(EntityItem::DEFAULT_COLLISIONS_WILL_MOVE),
|
||||||
|
|
||||||
_positionChanged(false),
|
_positionChanged(false),
|
||||||
_dimensionsChanged(false),
|
_dimensionsChanged(false),
|
||||||
|
@ -55,6 +57,8 @@ EntityItemProperties::EntityItemProperties() :
|
||||||
_angularVelocityChanged(false),
|
_angularVelocityChanged(false),
|
||||||
_angularDampingChanged(false),
|
_angularDampingChanged(false),
|
||||||
_visibleChanged(false),
|
_visibleChanged(false),
|
||||||
|
_ignoreForCollisionsChanged(false),
|
||||||
|
_collisionsWillMoveChanged(false),
|
||||||
|
|
||||||
_color(),
|
_color(),
|
||||||
_modelURL(""),
|
_modelURL(""),
|
||||||
|
@ -112,6 +116,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
||||||
CHECK_PROPERTY_CHANGE(PROP_REGISTRATION_POINT, registrationPoint);
|
CHECK_PROPERTY_CHANGE(PROP_REGISTRATION_POINT, registrationPoint);
|
||||||
CHECK_PROPERTY_CHANGE(PROP_ANGULAR_VELOCITY, angularVelocity);
|
CHECK_PROPERTY_CHANGE(PROP_ANGULAR_VELOCITY, angularVelocity);
|
||||||
CHECK_PROPERTY_CHANGE(PROP_ANGULAR_DAMPING, angularDamping);
|
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;
|
return changedProperties;
|
||||||
}
|
}
|
||||||
|
@ -150,6 +156,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex);
|
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex);
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFPS);
|
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFPS);
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel);
|
COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel);
|
||||||
|
COPY_PROPERTY_TO_QSCRIPTVALUE(ignoreForCollisions);
|
||||||
|
COPY_PROPERTY_TO_QSCRIPTVALUE(collisionsWillMove);
|
||||||
|
|
||||||
// Sitting properties support
|
// Sitting properties support
|
||||||
QScriptValue sittingPoints = engine->newObject();
|
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(animationFPS, setAnimationFPS);
|
||||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFrameIndex, setAnimationFrameIndex);
|
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFrameIndex, setAnimationFrameIndex);
|
||||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(glowLevel, setGlowLevel);
|
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(glowLevel, setGlowLevel);
|
||||||
|
COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(ignoreForCollisions, setIgnoreForCollisions);
|
||||||
|
COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(collisionsWillMove, setCollisionsWillMove);
|
||||||
|
|
||||||
_lastEdited = usecTimestampNow();
|
_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_VELOCITY, appendValue, properties.getAngularVelocity());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, appendValue, properties.getAngularDamping());
|
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, appendValue, properties.getAngularDamping());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_VISIBLE, appendValue, properties.getVisible());
|
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) {
|
if (propertyCount > 0) {
|
||||||
int endOfEntityItemData = packetData->getUncompressedByteOffset();
|
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_VELOCITY, glm::vec3, setAngularVelocity);
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_DAMPING, float, setAngularDamping);
|
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_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;
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,8 +62,10 @@ enum EntityPropertyList {
|
||||||
PROP_REGISTRATION_POINT,
|
PROP_REGISTRATION_POINT,
|
||||||
PROP_ANGULAR_VELOCITY,
|
PROP_ANGULAR_VELOCITY,
|
||||||
PROP_ANGULAR_DAMPING,
|
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;
|
typedef PropertyFlags<EntityPropertyList> EntityPropertyFlags;
|
||||||
|
@ -221,6 +223,12 @@ public:
|
||||||
bool getVisible() const { return _visible; }
|
bool getVisible() const { return _visible; }
|
||||||
void setVisible(bool value) { _visible = value; _visibleChanged = true; }
|
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; }
|
void setLastEdited(quint64 usecTime) { _lastEdited = usecTime; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -247,6 +255,8 @@ private:
|
||||||
glm::vec3 _angularVelocity;
|
glm::vec3 _angularVelocity;
|
||||||
float _angularDamping;
|
float _angularDamping;
|
||||||
bool _visible;
|
bool _visible;
|
||||||
|
bool _ignoreForCollisions;
|
||||||
|
bool _collisionsWillMove;
|
||||||
|
|
||||||
bool _positionChanged;
|
bool _positionChanged;
|
||||||
bool _dimensionsChanged;
|
bool _dimensionsChanged;
|
||||||
|
@ -261,6 +271,8 @@ private:
|
||||||
bool _angularVelocityChanged;
|
bool _angularVelocityChanged;
|
||||||
bool _angularDampingChanged;
|
bool _angularDampingChanged;
|
||||||
bool _visibleChanged;
|
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
|
// 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...
|
// named getter/setters, but we want to move them to generic types...
|
||||||
|
|
|
@ -555,8 +555,12 @@ bool EntityTreeElement::findShapeCollisions(const Shape* shape, CollisionList& c
|
||||||
QList<EntityItem*>::const_iterator entityEnd = _entityItems->end();
|
QList<EntityItem*>::const_iterator entityEnd = _entityItems->end();
|
||||||
while(entityItr != entityEnd) {
|
while(entityItr != entityEnd) {
|
||||||
EntityItem* entity = (*entityItr);
|
EntityItem* entity = (*entityItr);
|
||||||
|
|
||||||
|
// entities that are set for ignore for collisions then don't consider them for collision
|
||||||
const Shape* otherCollisionShape = &entity->getCollisionShapeInMeters();
|
const Shape* otherCollisionShape = &entity->getCollisionShapeInMeters();
|
||||||
if (shape != otherCollisionShape) {
|
|
||||||
|
bool ignoreForCollisions = entity->getIgnoreForCollisions();
|
||||||
|
if (shape != otherCollisionShape && !ignoreForCollisions) {
|
||||||
if (ShapeCollider::collideShapes(shape, otherCollisionShape, collisions)) {
|
if (ShapeCollider::collideShapes(shape, otherCollisionShape, collisions)) {
|
||||||
CollisionInfo* lastCollision = collisions.getLastCollision();
|
CollisionInfo* lastCollision = collisions.getLastCollision();
|
||||||
lastCollision->_extraData = entity;
|
lastCollision->_extraData = entity;
|
||||||
|
|
|
@ -80,6 +80,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) {
|
||||||
|
|
||||||
// if this is a relative path then handle it as a relative viewpoint
|
// if this is a relative path then handle it as a relative viewpoint
|
||||||
handleRelativeViewpoint(lookupUrl.path());
|
handleRelativeViewpoint(lookupUrl.path());
|
||||||
|
emit lookupResultsFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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
|
// we've been told that this result exists but is offline, emit our signal so the application can handle
|
||||||
emit lookupResultIsOffline();
|
emit lookupResultIsOffline();
|
||||||
}
|
}
|
||||||
|
emit lookupResultsFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressManager::handleAPIError(QNetworkReply& errorReply) {
|
void AddressManager::handleAPIError(QNetworkReply& errorReply) {
|
||||||
|
@ -157,6 +159,7 @@ void AddressManager::handleAPIError(QNetworkReply& errorReply) {
|
||||||
if (errorReply.error() == QNetworkReply::ContentNotFoundError) {
|
if (errorReply.error() == QNetworkReply::ContentNotFoundError) {
|
||||||
emit lookupResultIsNotFound();
|
emit lookupResultIsNotFound();
|
||||||
}
|
}
|
||||||
|
emit lookupResultsFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString GET_PLACE = "/api/v1/places/%1";
|
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) {
|
void AddressManager::attemptPlaceNameLookup(const QString& lookupString) {
|
||||||
// assume this is a place name and see if we can get any info on it
|
// assume this is a place name and see if we can get any info on it
|
||||||
QString placeName = QUrl::toPercentEncoding(lookupString);
|
QString placeName = QUrl::toPercentEncoding(lookupString);
|
||||||
AccountManager::getInstance().authenticatedRequest(GET_PLACE.arg(placeName),
|
AccountManager::getInstance().unauthenticatedRequest(GET_PLACE.arg(placeName),
|
||||||
QNetworkAccessManager::GetOperation,
|
QNetworkAccessManager::GetOperation,
|
||||||
apiCallbackParameters());
|
apiCallbackParameters());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AddressManager::handleNetworkAddress(const QString& lookupString) {
|
bool AddressManager::handleNetworkAddress(const QString& lookupString) {
|
||||||
|
@ -180,6 +183,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) {
|
||||||
|
|
||||||
if (hostnameRegex.indexIn(lookupString) != -1) {
|
if (hostnameRegex.indexIn(lookupString) != -1) {
|
||||||
emit possibleDomainChangeRequired(hostnameRegex.cap(0));
|
emit possibleDomainChangeRequired(hostnameRegex.cap(0));
|
||||||
|
emit lookupResultsFinished();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,6 +191,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) {
|
||||||
|
|
||||||
if (ipAddressRegex.indexIn(lookupString) != -1) {
|
if (ipAddressRegex.indexIn(lookupString) != -1) {
|
||||||
emit possibleDomainChangeRequired(ipAddressRegex.cap(0));
|
emit possibleDomainChangeRequired(ipAddressRegex.cap(0));
|
||||||
|
emit lookupResultsFinished();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,7 +268,7 @@ bool AddressManager::handleUsername(const QString& lookupString) {
|
||||||
void AddressManager::goToUser(const QString& username) {
|
void AddressManager::goToUser(const QString& username) {
|
||||||
QString formattedUsername = QUrl::toPercentEncoding(username);
|
QString formattedUsername = QUrl::toPercentEncoding(username);
|
||||||
// this is a username - pull the captured name and lookup that user's location
|
// this is a username - pull the captured name and lookup that user's location
|
||||||
AccountManager::getInstance().authenticatedRequest(GET_USER_LOCATION.arg(formattedUsername),
|
AccountManager::getInstance().unauthenticatedRequest(GET_USER_LOCATION.arg(formattedUsername),
|
||||||
QNetworkAccessManager::GetOperation,
|
QNetworkAccessManager::GetOperation,
|
||||||
apiCallbackParameters());
|
apiCallbackParameters());
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ public slots:
|
||||||
void handleAPIError(QNetworkReply& errorReply);
|
void handleAPIError(QNetworkReply& errorReply);
|
||||||
void goToUser(const QString& username);
|
void goToUser(const QString& username);
|
||||||
signals:
|
signals:
|
||||||
|
void lookupResultsFinished();
|
||||||
void lookupResultIsOffline();
|
void lookupResultIsOffline();
|
||||||
void lookupResultIsNotFound();
|
void lookupResultIsNotFound();
|
||||||
void possibleDomainChangeRequired(const QString& newHostname);
|
void possibleDomainChangeRequired(const QString& newHostname);
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <QtCore/QJsonDocument>
|
#include <QtCore/QJsonDocument>
|
||||||
#include <QtCore/QJsonArray>
|
#include <QtCore/QJsonArray>
|
||||||
#include <QtCore/QJsonObject>
|
#include <QtCore/QJsonObject>
|
||||||
|
#include <QtCore/QStandardPaths>
|
||||||
#include <QtCore/QVariant>
|
#include <QtCore/QVariant>
|
||||||
|
|
||||||
#include "HifiConfigVariantMap.h"
|
#include "HifiConfigVariantMap.h"
|
||||||
|
@ -75,30 +76,100 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
|
||||||
// we have a config file - try and read it
|
// we have a config file - try and read it
|
||||||
configFilePath = argumentList[configIndex + 1];
|
configFilePath = argumentList[configIndex + 1];
|
||||||
} else {
|
} else {
|
||||||
// no config file - try to read a file at resources/config.json
|
// no config file - try to read a file config.json at the system config path
|
||||||
configFilePath = QCoreApplication::applicationDirPath() + "/resources/config.json";
|
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;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,13 @@
|
||||||
class HifiConfigVariantMap {
|
class HifiConfigVariantMap {
|
||||||
public:
|
public:
|
||||||
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
|
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
|
#endif // hifi_HifiConfigVariantMap_h
|
||||||
|
|
Loading…
Reference in a new issue