diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp
index 3670653493..681cc58cb3 100644
--- a/assignment-client/src/audio/AudioMixer.cpp
+++ b/assignment-client/src/audio/AudioMixer.cpp
@@ -62,6 +62,7 @@
#include "AudioMixer.h"
const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f;
+const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.18;
const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer";
@@ -82,6 +83,7 @@ AudioMixer::AudioMixer(const QByteArray& packet) :
_trailingSleepRatio(1.0f),
_minAudibilityThreshold(LOUDNESS_TO_DISTANCE_RATIO / 2.0f),
_performanceThrottlingRatio(0.0f),
+ _attenuationPerDoublingInDistance(DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE),
_numStatFrames(0),
_sumListeners(0),
_sumMixes(0),
@@ -104,7 +106,6 @@ AudioMixer::~AudioMixer() {
}
const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f;
-const float ATTENUATION_AMOUNT_PER_DOUBLING_IN_DISTANCE = 0.18f;
const float RADIUS_OF_HEAD = 0.076f;
int AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData* listenerNodeData,
@@ -210,7 +211,7 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData* l
if (shouldDistanceAttenuate && (distanceBetween >= ATTENUATION_BEGINS_AT_DISTANCE)) {
// calculate the distance coefficient using the distance to this node
float distanceCoefficient = 1 - (logf(distanceBetween / ATTENUATION_BEGINS_AT_DISTANCE) / logf(2.0f)
- * ATTENUATION_AMOUNT_PER_DOUBLING_IN_DISTANCE);
+ * _attenuationPerDoublingInDistance);
if (distanceCoefficient < 0) {
distanceCoefficient = 0;
@@ -643,7 +644,7 @@ void AudioMixer::run() {
QJsonObject audioGroupObject = settingsObject[AUDIO_GROUP_KEY].toObject();
// check the payload to see if we have asked for dynamicJitterBuffer support
- const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "dynamic-jitter-buffer";
+ const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "dynamic_jitter_buffer";
_streamSettings._dynamicJitterBuffers = audioGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool();
if (_streamSettings._dynamicJitterBuffers) {
qDebug() << "Enable dynamic jitter buffers.";
@@ -652,21 +653,21 @@ void AudioMixer::run() {
}
bool ok;
- const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "static-desired-jitter-buffer-frames";
+ const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "static_desired_jitter_buffer_frames";
_streamSettings._staticDesiredJitterBufferFrames = audioGroupObject[DESIRED_JITTER_BUFFER_FRAMES_KEY].toString().toInt(&ok);
if (!ok) {
_streamSettings._staticDesiredJitterBufferFrames = DEFAULT_STATIC_DESIRED_JITTER_BUFFER_FRAMES;
}
qDebug() << "Static desired jitter buffer frames:" << _streamSettings._staticDesiredJitterBufferFrames;
- const QString MAX_FRAMES_OVER_DESIRED_JSON_KEY = "max-frames-over-desired";
+ const QString MAX_FRAMES_OVER_DESIRED_JSON_KEY = "max_frames_over_desired";
_streamSettings._maxFramesOverDesired = audioGroupObject[MAX_FRAMES_OVER_DESIRED_JSON_KEY].toString().toInt(&ok);
if (!ok) {
_streamSettings._maxFramesOverDesired = DEFAULT_MAX_FRAMES_OVER_DESIRED;
}
qDebug() << "Max frames over desired:" << _streamSettings._maxFramesOverDesired;
- const QString USE_STDEV_FOR_DESIRED_CALC_JSON_KEY = "use-stdev-for-desired-calc";
+ const QString USE_STDEV_FOR_DESIRED_CALC_JSON_KEY = "use_stdev_for_desired_calc";
_streamSettings._useStDevForJitterCalc = audioGroupObject[USE_STDEV_FOR_DESIRED_CALC_JSON_KEY].toBool();
if (_streamSettings._useStDevForJitterCalc) {
qDebug() << "Using Philip's stdev method for jitter calc if dynamic jitter buffers enabled";
@@ -674,28 +675,28 @@ void AudioMixer::run() {
qDebug() << "Using Fred's max-gap method for jitter calc if dynamic jitter buffers enabled";
}
- const QString WINDOW_STARVE_THRESHOLD_JSON_KEY = "window-starve-threshold";
+ const QString WINDOW_STARVE_THRESHOLD_JSON_KEY = "window_starve_threshold";
_streamSettings._windowStarveThreshold = audioGroupObject[WINDOW_STARVE_THRESHOLD_JSON_KEY].toString().toInt(&ok);
if (!ok) {
_streamSettings._windowStarveThreshold = DEFAULT_WINDOW_STARVE_THRESHOLD;
}
qDebug() << "Window A starve threshold:" << _streamSettings._windowStarveThreshold;
- const QString WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES_JSON_KEY = "window-seconds-for-desired-calc-on-too-many-starves";
+ const QString WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES_JSON_KEY = "window_seconds_for_desired_calc_on_too_many_starves";
_streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = audioGroupObject[WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES_JSON_KEY].toString().toInt(&ok);
if (!ok) {
_streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES;
}
qDebug() << "Window A length:" << _streamSettings._windowSecondsForDesiredCalcOnTooManyStarves << "seconds";
- const QString WINDOW_SECONDS_FOR_DESIRED_REDUCTION_JSON_KEY = "window-seconds-for-desired-reduction";
+ const QString WINDOW_SECONDS_FOR_DESIRED_REDUCTION_JSON_KEY = "window_seconds_for_desired_reduction";
_streamSettings._windowSecondsForDesiredReduction = audioGroupObject[WINDOW_SECONDS_FOR_DESIRED_REDUCTION_JSON_KEY].toString().toInt(&ok);
if (!ok) {
_streamSettings._windowSecondsForDesiredReduction = DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION;
}
qDebug() << "Window B length:" << _streamSettings._windowSecondsForDesiredReduction << "seconds";
- const QString REPETITION_WITH_FADE_JSON_KEY = "repetition-with-fade";
+ const QString REPETITION_WITH_FADE_JSON_KEY = "repetition_with_fade";
_streamSettings._repetitionWithFade = audioGroupObject[REPETITION_WITH_FADE_JSON_KEY].toBool();
if (_streamSettings._repetitionWithFade) {
qDebug() << "Repetition with fade enabled";
@@ -703,13 +704,13 @@ void AudioMixer::run() {
qDebug() << "Repetition with fade disabled";
}
- const QString PRINT_STREAM_STATS_JSON_KEY = "print-stream-stats";
+ const QString PRINT_STREAM_STATS_JSON_KEY = "print_stream_stats";
_printStreamStats = audioGroupObject[PRINT_STREAM_STATS_JSON_KEY].toBool();
if (_printStreamStats) {
qDebug() << "Stream stats will be printed to stdout";
}
- const QString FILTER_KEY = "enable-filter";
+ const QString FILTER_KEY = "enable_filter";
if (audioGroupObject[FILTER_KEY].isBool()) {
_enableFilter = audioGroupObject[FILTER_KEY].toBool();
}
@@ -717,7 +718,7 @@ void AudioMixer::run() {
qDebug() << "Filter enabled";
}
- const QString UNATTENUATED_ZONE_KEY = "unattenuated-zone";
+ const QString UNATTENUATED_ZONE_KEY = "unattenuated_zone";
QString unattenuatedZoneString = audioGroupObject[UNATTENUATED_ZONE_KEY].toString();
if (!unattenuatedZoneString.isEmpty()) {
@@ -740,6 +741,16 @@ void AudioMixer::run() {
qDebug() << "Buffers inside this zone will not be attenuated inside a box with center at"
<< QString("%1, %2, %3").arg(destinationCenter.x).arg(destinationCenter.y).arg(destinationCenter.z);
}
+
+ const QString ATTENATION_PER_DOULING_IN_DISTANCE = "attenuation_per_doubling_in_distance";
+ if (audioGroupObject[ATTENATION_PER_DOULING_IN_DISTANCE].isString()) {
+ bool ok = false;
+ float attenuation = audioGroupObject[ATTENATION_PER_DOULING_IN_DISTANCE].toString().toFloat(&ok);
+ if (ok) {
+ _attenuationPerDoublingInDistance = attenuation;
+ qDebug() << "Attenuation per doubling in distance changed to" << _attenuationPerDoublingInDistance;
+ }
+ }
}
int nextFrame = 0;
diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h
index 6c25fe21d9..4d08d480f8 100644
--- a/assignment-client/src/audio/AudioMixer.h
+++ b/assignment-client/src/audio/AudioMixer.h
@@ -69,6 +69,7 @@ private:
float _trailingSleepRatio;
float _minAudibilityThreshold;
float _performanceThrottlingRatio;
+ float _attenuationPerDoublingInDistance;
int _numStatFrames;
int _sumListeners;
int _sumMixes;
diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json
index 8fb7bddb89..24e9a5b63b 100644
--- a/domain-server/resources/describe-settings.json
+++ b/domain-server/resources/describe-settings.json
@@ -4,9 +4,9 @@
"label": "Metaverse Registration",
"settings": [
{
- "name": "access-token",
+ "name": "access_token",
"label": "Access Token",
- "help": "This is an access token generated on the My Tokens page of your High Fidelity account. Generate a token with the 'domains' scope and paste it here. This is required to associate this domain-server with a domain in your account."
+ "help": "This is an access token generated on the My Tokens page of your High Fidelity account. Generate a token with the 'domains' scope and paste it here. This is required to associate this domain-server with a domain in your account."
},
{
"name": "id",
@@ -20,12 +20,12 @@
"label": "Security",
"settings": [
{
- "name": "http-username",
+ "name": "http_username",
"label": "HTTP Username",
"help": "Username used for basic HTTP authentication."
},
{
- "name": "http-password",
+ "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.",
@@ -39,20 +39,28 @@
"assignment-types": [0],
"settings": [
{
- "name": "enable-filter",
+ "name": "enable_filter",
"type": "checkbox",
"label": "Enable Positional Filter",
"help": "positional audio stream uses lowpass filter",
"default": true
},
{
- "name": "unattenuated-zone",
+ "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",
+ "name": "attenuation_per_doubling_in_distance",
+ "label": "Attenuattion per doubling in distance",
+ "help": "Factor between 0.0 and 1.0 (0.0: No attenuation, 1.0: extreme attenuation)",
+ "placeholder": "0.18",
+ "default": "0.18",
+ "advanced": true
+ },
+ {
+ "name": "dynamic_jitter_buffer",
"type": "checkbox",
"label": "Dynamic Jitter Buffers",
"help": "dynamically buffer client audio based on perceived jitter in packet receipt timing",
@@ -60,7 +68,7 @@
"advanced": true
},
{
- "name": "static-desired-jitter-buffer-frames",
+ "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",
@@ -68,7 +76,7 @@
"advanced": true
},
{
- "name": "max-frames-over-desired",
+ "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",
@@ -76,7 +84,7 @@
"advanced": true
},
{
- "name": "use-stdev-for-desired-calc",
+ "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)",
@@ -84,7 +92,7 @@
"advanced": true
},
{
- "name": "window-starve-threshold",
+ "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",
@@ -92,7 +100,7 @@
"advanced": true
},
{
- "name": "window-seconds-for-desired-calc-on-too-many-starves",
+ "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",
@@ -100,7 +108,7 @@
"advanced": true
},
{
- "name": "window-seconds-for-desired-reduction",
+ "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",
@@ -108,7 +116,7 @@
"advanced": true
},
{
- "name": "repetition-with-fade",
+ "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",
@@ -116,7 +124,7 @@
"advanced": true
},
{
- "name": "I-print-stream-stats",
+ "name": "print_stream_stats",
"type": "checkbox",
"label": "Print Stream Stats:",
"help": "audio upstream and downstream stats of each agent printed to audio-mixer stdout",
diff --git a/domain-server/resources/web/assignment/index.shtml b/domain-server/resources/web/assignment/index.shtml
index 27c84be985..b8f4fbb637 100644
--- a/domain-server/resources/web/assignment/index.shtml
+++ b/domain-server/resources/web/assignment/index.shtml
@@ -6,7 +6,7 @@
-
+
diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css
index cef30b5782..60f493593a 100644
--- a/domain-server/resources/web/css/style.css
+++ b/domain-server/resources/web/css/style.css
@@ -52,6 +52,10 @@ span.port {
color: red;
}
+.locked {
+ color: blue;
+}
+
.advanced-setting {
display: none;
}
@@ -69,4 +73,4 @@ span.port {
#small-save-button {
width: 100%;
margin-bottom: 15px;
-}
\ No newline at end of file
+}
diff --git a/domain-server/resources/web/footer.html b/domain-server/resources/web/footer.html
index 199a835cf1..4ab3ed0b31 100644
--- a/domain-server/resources/web/footer.html
+++ b/domain-server/resources/web/footer.html
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/domain-server/resources/web/js/bootbox.min.js b/domain-server/resources/web/js/bootbox.min.js
new file mode 100644
index 0000000000..a7ea24fe7a
--- /dev/null
+++ b/domain-server/resources/web/js/bootbox.min.js
@@ -0,0 +1,6 @@
+/**
+ * bootbox.js v4.3.0
+ *
+ * http://bootboxjs.com/license.txt
+ */
+!function(a,b){"use strict";"function"==typeof define&&define.amd?define(["jquery"],b):"object"==typeof exports?module.exports=b(require("jquery")):a.bootbox=b(a.jQuery)}(this,function a(b,c){"use strict";function d(a){var b=q[o.locale];return b?b[a]:q.en[a]}function e(a,c,d){a.stopPropagation(),a.preventDefault();var e=b.isFunction(d)&&d(a)===!1;e||c.modal("hide")}function f(a){var b,c=0;for(b in a)c++;return c}function g(a,c){var d=0;b.each(a,function(a,b){c(a,b,d++)})}function h(a){var c,d;if("object"!=typeof a)throw new Error("Please supply an object of options");if(!a.message)throw new Error("Please specify a message");return a=b.extend({},o,a),a.buttons||(a.buttons={}),a.backdrop=a.backdrop?"static":!1,c=a.buttons,d=f(c),g(c,function(a,e,f){if(b.isFunction(e)&&(e=c[a]={callback:e}),"object"!==b.type(e))throw new Error("button with key "+a+" must be an object");e.label||(e.label=a),e.className||(e.className=2>=d&&f===d-1?"btn-primary":"btn-default")}),a}function i(a,b){var c=a.length,d={};if(1>c||c>2)throw new Error("Invalid argument length");return 2===c||"string"==typeof a[0]?(d[b[0]]=a[0],d[b[1]]=a[1]):d=a[0],d}function j(a,c,d){return b.extend(!0,{},a,i(c,d))}function k(a,b,c,d){var e={className:"bootbox-"+a,buttons:l.apply(null,b)};return m(j(e,d,c),b)}function l(){for(var a={},b=0,c=arguments.length;c>b;b++){var e=arguments[b],f=e.toLowerCase(),g=e.toUpperCase();a[f]={label:d(g)}}return a}function m(a,b){var d={};return g(b,function(a,b){d[b]=!0}),g(a.buttons,function(a){if(d[a]===c)throw new Error("button key "+a+" is not allowed (options are "+b.join("\n")+")")}),a}var n={dialog:"",header:"",footer:"",closeButton:"× ",form:"",inputs:{text:" ",textarea:"",email:" ",select:" ",checkbox:"
",date:" ",time:" ",number:" ",password:" "}},o={locale:"en",backdrop:!0,animate:!0,className:null,closeButton:!0,show:!0,container:"body"},p={};p.alert=function(){var a;if(a=k("alert",["ok"],["message","callback"],arguments),a.callback&&!b.isFunction(a.callback))throw new Error("alert requires callback property to be a function when provided");return a.buttons.ok.callback=a.onEscape=function(){return b.isFunction(a.callback)?a.callback():!0},p.dialog(a)},p.confirm=function(){var a;if(a=k("confirm",["cancel","confirm"],["message","callback"],arguments),a.buttons.cancel.callback=a.onEscape=function(){return a.callback(!1)},a.buttons.confirm.callback=function(){return a.callback(!0)},!b.isFunction(a.callback))throw new Error("confirm requires a callback");return p.dialog(a)},p.prompt=function(){var a,d,e,f,h,i,k;if(f=b(n.form),d={className:"bootbox-prompt",buttons:l("cancel","confirm"),value:"",inputType:"text"},a=m(j(d,arguments,["title","callback"]),["cancel","confirm"]),i=a.show===c?!0:a.show,a.message=f,a.buttons.cancel.callback=a.onEscape=function(){return a.callback(null)},a.buttons.confirm.callback=function(){var c;switch(a.inputType){case"text":case"textarea":case"email":case"select":case"date":case"time":case"number":case"password":c=h.val();break;case"checkbox":var d=h.find("input:checked");c=[],g(d,function(a,d){c.push(b(d).val())})}return a.callback(c)},a.show=!1,!a.title)throw new Error("prompt requires a title");if(!b.isFunction(a.callback))throw new Error("prompt requires a callback");if(!n.inputs[a.inputType])throw new Error("invalid prompt type");switch(h=b(n.inputs[a.inputType]),a.inputType){case"text":case"textarea":case"email":case"date":case"time":case"number":case"password":h.val(a.value);break;case"select":var o={};if(k=a.inputOptions||[],!k.length)throw new Error("prompt with select requires options");g(k,function(a,d){var e=h;if(d.value===c||d.text===c)throw new Error("given options in wrong format");d.group&&(o[d.group]||(o[d.group]=b(" ").attr("label",d.group)),e=o[d.group]),e.append(""+d.text+" ")}),g(o,function(a,b){h.append(b)}),h.val(a.value);break;case"checkbox":var q=b.isArray(a.value)?a.value:[a.value];if(k=a.inputOptions||[],!k.length)throw new Error("prompt with checkbox requires options");if(!k[0].value||!k[0].text)throw new Error("given options in wrong format");h=b("
"),g(k,function(c,d){var e=b(n.inputs[a.inputType]);e.find("input").attr("value",d.value),e.find("label").append(d.text),g(q,function(a,b){b===d.value&&e.find("input").prop("checked",!0)}),h.append(e)})}return a.placeholder&&h.attr("placeholder",a.placeholder),a.pattern&&h.attr("pattern",a.pattern),f.append(h),f.on("submit",function(a){a.preventDefault(),a.stopPropagation(),e.find(".btn-primary").click()}),e=p.dialog(a),e.off("shown.bs.modal"),e.on("shown.bs.modal",function(){h.focus()}),i===!0&&e.modal("show"),e},p.dialog=function(a){a=h(a);var c=b(n.dialog),d=c.find(".modal-dialog"),f=c.find(".modal-body"),i=a.buttons,j="",k={onEscape:a.onEscape};if(g(i,function(a,b){j+=""+b.label+" ",k[a]=b.callback}),f.find(".bootbox-body").html(a.message),a.animate===!0&&c.addClass("fade"),a.className&&c.addClass(a.className),"large"===a.size&&d.addClass("modal-lg"),"small"===a.size&&d.addClass("modal-sm"),a.title&&f.before(n.header),a.closeButton){var l=b(n.closeButton);a.title?c.find(".modal-header").prepend(l):l.css("margin-top","-10px").prependTo(f)}return a.title&&c.find(".modal-title").html(a.title),j.length&&(f.after(n.footer),c.find(".modal-footer").html(j)),c.on("hidden.bs.modal",function(a){a.target===this&&c.remove()}),c.on("shown.bs.modal",function(){c.find(".btn-primary:first").focus()}),c.on("escape.close.bb",function(a){k.onEscape&&e(a,c,k.onEscape)}),c.on("click",".modal-footer button",function(a){var d=b(this).data("bb-handler");e(a,c,k[d])}),c.on("click",".bootbox-close-button",function(a){e(a,c,k.onEscape)}),c.on("keyup",function(a){27===a.which&&c.trigger("escape.close.bb")}),b(a.container).append(c),c.modal({backdrop:a.backdrop,keyboard:!1,show:!1}),a.show&&c.modal("show"),c},p.setDefaults=function(){var a={};2===arguments.length?a[arguments[0]]=arguments[1]:a=arguments[0],b.extend(o,a)},p.hideAll=function(){return b(".bootbox").modal("hide"),p};var q={br:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Sim"},cs:{OK:"OK",CANCEL:"Zrušit",CONFIRM:"Potvrdit"},da:{OK:"OK",CANCEL:"Annuller",CONFIRM:"Accepter"},de:{OK:"OK",CANCEL:"Abbrechen",CONFIRM:"Akzeptieren"},el:{OK:"Εντάξει",CANCEL:"Ακύρωση",CONFIRM:"Επιβεβαίωση"},en:{OK:"OK",CANCEL:"Cancel",CONFIRM:"OK"},es:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Aceptar"},et:{OK:"OK",CANCEL:"Katkesta",CONFIRM:"OK"},fi:{OK:"OK",CANCEL:"Peruuta",CONFIRM:"OK"},fr:{OK:"OK",CANCEL:"Annuler",CONFIRM:"D'accord"},he:{OK:"אישור",CANCEL:"ביטול",CONFIRM:"אישור"},id:{OK:"OK",CANCEL:"Batal",CONFIRM:"OK"},it:{OK:"OK",CANCEL:"Annulla",CONFIRM:"Conferma"},ja:{OK:"OK",CANCEL:"キャンセル",CONFIRM:"確認"},lt:{OK:"Gerai",CANCEL:"Atšaukti",CONFIRM:"Patvirtinti"},lv:{OK:"Labi",CANCEL:"Atcelt",CONFIRM:"Apstiprināt"},nl:{OK:"OK",CANCEL:"Annuleren",CONFIRM:"Accepteren"},no:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},pl:{OK:"OK",CANCEL:"Anuluj",CONFIRM:"Potwierdź"},pt:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Confirmar"},ru:{OK:"OK",CANCEL:"Отмена",CONFIRM:"Применить"},sv:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},tr:{OK:"Tamam",CANCEL:"İptal",CONFIRM:"Onayla"},zh_CN:{OK:"OK",CANCEL:"取消",CONFIRM:"确认"},zh_TW:{OK:"OK",CANCEL:"取消",CONFIRM:"確認"}};return p.init=function(c){return a(c||b)},p});
\ No newline at end of file
diff --git a/domain-server/resources/web/js/jquery-2.1.1.min.js b/domain-server/resources/web/js/jquery.min.js
similarity index 100%
rename from domain-server/resources/web/js/jquery-2.1.1.min.js
rename to domain-server/resources/web/js/jquery.min.js
diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js
index 8bb02e6e94..fa05b1cce9 100644
--- a/domain-server/resources/web/js/settings.js
+++ b/domain-server/resources/web/js/settings.js
@@ -3,8 +3,8 @@ var Settings = {
};
var viewHelpers = {
- getFormGroup: function(groupName, setting, values, isAdvanced) {
- setting_id = groupName + "_" + setting.name
+ getFormGroup: function(groupName, setting, values, isAdvanced, isLocked) {
+ setting_name = groupName + "." + setting.name
form_group = ""
@@ -16,24 +16,29 @@ var viewHelpers = {
setting_value = ""
}
+ label_class = 'control-label'
+ if (isLocked) {
+ label_class += ' locked'
+ }
+
if (setting.type === 'checkbox') {
- form_group += "
" + setting.label + " "
- form_group += "
"
return form_group
}
@@ -81,6 +86,10 @@ $(document).ready(function(){
$(this).blur()
})
+
+ $('#settings-form').on('click', '#choose-domain-btn', function(){
+ chooseFromHighFidelityDomains($(this))
+ })
var panelsSource = $('#panels-template').html()
Settings.panelsTemplate = _.template(panelsSource)
@@ -100,10 +109,24 @@ function reloadSettings() {
$('.nav-stacked').html(Settings.sidebarTemplate(data))
$('#panels').html(Settings.panelsTemplate(data))
- Settings.initialValues = form2js('settings-form', "_", false, cleanupFormValues, true);
+ Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true);
+
+ // add tooltip to locked settings
+ $('label.locked').tooltip({
+ placement: 'right',
+ title: 'This setting is in the master config file and cannot be changed'
+ })
+
+ appendDomainSelectionModal()
});
}
+function appendDomainSelectionModal() {
+ var metaverseInput = $("[name='metaverse.id']");
+ var chooseButton = $("
Choose ID from my domains ");
+ metaverseInput.after(chooseButton);
+}
+
var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please try again!";
$('body').on('click', '.save-button', function(e){
@@ -113,7 +136,9 @@ $('body').on('click', '.save-button', function(e){
});
// 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);
+
+ console.log(formJSON);
// re-enable all inputs
$("input").each(function(){
@@ -148,13 +173,13 @@ function badgeSidebarForDifferences(changedInput) {
var panelParentID = changedInput.closest('.panel').attr('id')
// get a JSON representation of that section
- var rootJSON = form2js(panelParentID, "_", false, cleanupFormValues, true);
+ 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]) {
+ if (panelJSON[setting] != Settings.initialValues[panelParentID][setting]) {
badgeValue += 1
}
}
@@ -194,7 +219,7 @@ function showRestartModal() {
function cleanupFormValues(node) {
if (node.type && node.type === 'checkbox') {
- return { name: node.id, value: node.checked ? true : false };
+ return { name: node.name, value: node.checked ? true : false };
} else {
return false;
}
@@ -206,4 +231,71 @@ function showAlertMessage(message, isSuccess) {
alertBox.addClass(isSuccess ? 'alert-success' : 'alert-danger');
alertBox.html(message);
alertBox.fadeIn();
+}
+
+function chooseFromHighFidelityDomains(clickedButton) {
+ // setup the modal to help user pick their domain
+ if (Settings.initialValues.metaverse.access_token) {
+
+ // add a spinner to the choose button
+ clickedButton.html("Loading domains...")
+ clickedButton.attr('disabled', 'disabled')
+
+ // get a list of user domains from data-web
+ data_web_domains_url = "https://data.highfidelity.io/api/v1/domains?access_token="
+ $.getJSON(data_web_domains_url + Settings.initialValues.metaverse.access_token, function(data){
+
+ modal_buttons = {
+ cancel: {
+ label: 'Cancel',
+ className: 'btn-default'
+ }
+ }
+
+ if (data.data.domains.length) {
+ // setup a select box for the returned domains
+ modal_body = "
Choose the High Fidelity domain you want this domain-server to represent. This will set your domain ID on the settings page.
"
+ domain_select = $("
")
+ _.each(data.data.domains, function(domain){
+ domain_select.append("
" + domain.name + " ")
+ })
+ modal_body += "
Domains " + domain_select[0].outerHTML
+ modal_buttons["success"] = {
+ label: 'Choose domain',
+ callback: function() {
+ domainID = $('#domain-name-select').val()
+ // set the domain ID on the form
+ $("[name='metaverse.id']").val(domainID).change();
+ }
+ }
+ } else {
+ modal_buttons["success"] = {
+ label: 'Create new domain',
+ callback: function() {
+ window.open("https://data.highfidelity.io/domains", '_blank');
+ }
+ }
+ modal_body = "
You do not have any domains in your High Fidelity account." +
+ " Go to your domains page to create a new one. Once your domain is created re-open this dialog to select it.
"
+ }
+
+
+ bootbox.dialog({
+ title: "Choose matching domain",
+ message: modal_body,
+ buttons: modal_buttons
+ })
+
+ // remove the spinner from the choose button
+ clickedButton.html("Choose from my domains")
+ clickedButton.removeAttr('disabled')
+ })
+
+ } else {
+ bootbox.alert({
+ message: "You must have an access token to query your High Fidelity domains.
" +
+ "Please follow the instructions on the settings page to add an access token.",
+ title: "Access token required"
+ })
+ }
}
\ No newline at end of file
diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml
index 54fbb02eca..18b1f40d60 100644
--- a/domain-server/resources/web/settings/index.shtml
+++ b/domain-server/resources/web/settings/index.shtml
@@ -38,10 +38,12 @@
<% split_settings = _.partition(group.settings, function(value, index) { return !value.advanced }) %>
<% _.each(split_settings[0], function(setting) { %>
- <%= getFormGroup(group.name, setting, values, false) %>
+ <%= getFormGroup(group.name, setting, values, false,
+ (_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
<% }); %>
<% _.each(split_settings[1], function(setting) { %>
- <%= getFormGroup(group.name, setting, values, true) %>
+ <%= getFormGroup(group.name, setting, values, true,
+ (_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
<% }); %>
@@ -73,6 +75,7 @@
+
\ No newline at end of file
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index b1ed765be6..fb475f681d 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -57,7 +57,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
setApplicationName("domain-server");
QSettings::setDefaultFormat(QSettings::IniFormat);
- _settingsManager.loadSettingsMap(arguments());
+ _settingsManager.setupConfigMap(arguments());
installNativeEventFilter(&_shutdownEventListener);
connect(&_shutdownEventListener, SIGNAL(receivedCloseEvent()), SLOT(quit()));
@@ -81,6 +81,11 @@ DomainServer::DomainServer(int argc, char* argv[]) :
void DomainServer::restart() {
qDebug() << "domain-server is restarting.";
+
+ // make sure all static instances are reset
+ LimitedNodeList::getInstance()->reset();
+ AccountManager::getInstance(true);
+
exit(DomainServer::EXIT_CODE_REBOOT);
}
@@ -1396,8 +1401,8 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
- const QString BASIC_AUTH_USERNAME_KEY_PATH = "security.http-username";
- const QString BASIC_AUTH_PASSWORD_KEY_PATH = "security.http-password";
+ 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.";
diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp
index d9b1d4d8d6..b5d7df65cf 100644
--- a/domain-server/src/DomainServerSettingsManager.cpp
+++ b/domain-server/src/DomainServerSettingsManager.cpp
@@ -28,7 +28,7 @@ const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings
DomainServerSettingsManager::DomainServerSettingsManager() :
_descriptionArray(),
- _settingsMap()
+ _configMap()
{
// load the description object from the settings description
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
@@ -37,11 +37,30 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
_descriptionArray = QJsonDocument::fromJson(descriptionFile.readAll()).array();
}
-void DomainServerSettingsManager::loadSettingsMap(const QStringList& argumentList) {
- _settingsMap = HifiConfigVariantMap::mergeMasterConfigWithUserConfig(argumentList);
+void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
+ _configMap.loadMasterAndUserConfig(argumentList);
- // figure out where we are supposed to persist our settings to
- _settingsFilepath = HifiConfigVariantMap::userConfigFilepath(argumentList);
+ // for now we perform a temporary transition from http-username and http-password to http_username and http_password
+ const QVariant* oldUsername = valueForKeyPath(_configMap.getUserConfig(), "security.http-username");
+ const QVariant* oldPassword = valueForKeyPath(_configMap.getUserConfig(), "security.http-password");
+
+ if (oldUsername || oldPassword) {
+ QVariantMap& settingsMap = *reinterpret_cast(_configMap.getUserConfig()["security"].data());
+
+ // remove old keys, move to new format
+ if (oldUsername) {
+ settingsMap["http_username"] = oldUsername->toString();
+ settingsMap.remove("http-username");
+ }
+
+ if (oldPassword) {
+ settingsMap["http_password"] = oldPassword->toString();
+ settingsMap.remove("http-password");
+ }
+
+ // save the updated settings
+ persistToFile();
+ }
}
const QString SETTINGS_PATH = "/settings.json";
@@ -76,7 +95,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
QJsonObject postedObject = postedDocument.object();
// we recurse one level deep below each group for the appropriate setting
- recurseJSONObjectAndOverwriteSettings(postedObject, _settingsMap, _descriptionArray);
+ recurseJSONObjectAndOverwriteSettings(postedObject, _configMap.getUserConfig(), _descriptionArray);
// store whatever the current _settingsMap is to file
persistToFile();
@@ -94,10 +113,13 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
// setup a JSON Object with descriptions and non-omitted settings
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
+ const QString SETTINGS_RESPONSE_LOCKED_VALUES_KEY = "locked";
QJsonObject rootObject;
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
+ rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
+
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
}
@@ -145,7 +167,8 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
// we need to check if the settings map has a value for this setting
QVariant variantValue;
- QVariant settingsMapGroupValue = _settingsMap.value(groupObject[DESCRIPTION_NAME_KEY].toString());
+ QVariant settingsMapGroupValue = _configMap.getMergedConfig()
+ .value(groupObject[DESCRIPTION_NAME_KEY].toString());
if (!settingsMapGroupValue.isNull()) {
variantValue = settingsMapGroupValue.toMap().value(settingName);
@@ -239,23 +262,19 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
}
}
-QByteArray DomainServerSettingsManager::getJSONSettingsMap() const {
- return QJsonDocument::fromVariant(_settingsMap).toJson();
-}
-
void DomainServerSettingsManager::persistToFile() {
// make sure we have the dir the settings file is supposed to live in
- QFileInfo settingsFileInfo(_settingsFilepath);
+ QFileInfo settingsFileInfo(_configMap.getUserConfigFilename());
if (!settingsFileInfo.dir().exists()) {
settingsFileInfo.dir().mkpath(".");
}
- QFile settingsFile(_settingsFilepath);
+ QFile settingsFile(_configMap.getUserConfigFilename());
if (settingsFile.open(QIODevice::WriteOnly)) {
- settingsFile.write(getJSONSettingsMap());
+ settingsFile.write(QJsonDocument::fromVariant(_configMap.getUserConfig()).toJson());
} else {
qCritical("Could not write to JSON settings file. Unable to persist settings.");
}
diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h
index 29b773a354..b60cb32dfd 100644
--- a/domain-server/src/DomainServerSettingsManager.h
+++ b/domain-server/src/DomainServerSettingsManager.h
@@ -15,6 +15,7 @@
#include
#include
+#include
#include
class DomainServerSettingsManager : public QObject {
@@ -24,10 +25,9 @@ public:
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
- void loadSettingsMap(const QStringList& argumentList);
+ void setupConfigMap(const QStringList& argumentList);
- QByteArray getJSONSettingsMap() const;
- QVariantMap& getSettingsMap() { return _settingsMap; }
+ QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
private:
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant,
@@ -35,8 +35,7 @@ private:
void persistToFile();
QJsonArray _descriptionArray;
- QVariantMap _settingsMap;
- QString _settingsFilepath;
+ HifiConfigVariantMap _configMap;
};
#endif // hifi_DomainServerSettingsManager_h
\ No newline at end of file
diff --git a/examples/editModels.js b/examples/editModels.js
index c47c497a78..5c426c9fb3 100644
--- a/examples/editModels.js
+++ b/examples/editModels.js
@@ -2745,7 +2745,7 @@ function setupModelMenus() {
}
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." });
-
+ Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List", afterItem: "Models" });
Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" });
Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" });
Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" });
@@ -2796,6 +2796,128 @@ var dimensionY;
var dimensionZ;
var rescalePercentage;
+function showPropertiesForm() {
+ propertiesForEditedEntity = Entities.getEntityProperties(editModelID);
+ var properties = propertiesForEditedEntity;
+
+ var array = new Array();
+ var index = 0;
+ var decimals = 3;
+ if (properties.type == "Model") {
+ array.push({ label: "Model URL:", value: properties.modelURL });
+ index++;
+ array.push({ label: "Animation URL:", value: properties.animationURL });
+ index++;
+ array.push({ label: "Animation is playing:", value: properties.animationIsPlaying });
+ index++;
+ array.push({ label: "Animation FPS:", value: properties.animationFPS });
+ index++;
+ array.push({ label: "Animation Frame:", value: properties.animationFrameIndex });
+ index++;
+ }
+ array.push({ label: "Position:", type: "header" });
+ index++;
+ array.push({ label: "X:", value: properties.position.x.toFixed(decimals) });
+ index++;
+ array.push({ label: "Y:", value: properties.position.y.toFixed(decimals) });
+ index++;
+ array.push({ label: "Z:", value: properties.position.z.toFixed(decimals) });
+ index++;
+
+ array.push({ label: "Registration X:", value: properties.registrationPoint.x.toFixed(decimals) });
+ index++;
+ array.push({ label: "Registration Y:", value: properties.registrationPoint.y.toFixed(decimals) });
+ index++;
+ array.push({ label: "Registration Z:", value: properties.registrationPoint.z.toFixed(decimals) });
+ index++;
+
+ array.push({ label: "Rotation:", type: "header" });
+ index++;
+ var angles = Quat.safeEulerAngles(properties.rotation);
+ array.push({ label: "Pitch:", value: angles.x.toFixed(decimals) });
+ index++;
+ array.push({ label: "Yaw:", value: angles.y.toFixed(decimals) });
+ index++;
+ array.push({ label: "Roll:", value: angles.z.toFixed(decimals) });
+ index++;
+
+ array.push({ label: "Dimensions:", type: "header" });
+ index++;
+ array.push({ label: "Width:", value: properties.dimensions.x.toFixed(decimals) });
+ dimensionX = index;
+ index++;
+ array.push({ label: "Height:", value: properties.dimensions.y.toFixed(decimals) });
+ dimensionY = index;
+ index++;
+ array.push({ label: "Depth:", value: properties.dimensions.z.toFixed(decimals) });
+ dimensionZ = index;
+ index++;
+ array.push({ label: "", type: "inlineButton", buttonLabel: "Reset to Natural Dimensions", name: "resetDimensions" });
+ index++;
+ array.push({ label: "Rescale Percentage:", value: 100 });
+ rescalePercentage = index;
+ index++;
+ array.push({ label: "", type: "inlineButton", buttonLabel: "Rescale", name: "rescaleDimensions" });
+ index++;
+
+ array.push({ label: "Velocity:", type: "header" });
+ index++;
+ array.push({ label: "Linear X:", value: properties.velocity.x.toFixed(decimals) });
+ index++;
+ array.push({ label: "Linear Y:", value: properties.velocity.y.toFixed(decimals) });
+ index++;
+ array.push({ label: "Linear Z:", value: properties.velocity.z.toFixed(decimals) });
+ index++;
+ array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) });
+ index++;
+ array.push({ label: "Angular Pitch:", value: properties.angularVelocity.x.toFixed(decimals) });
+ index++;
+ array.push({ label: "Angular Yaw:", value: properties.angularVelocity.y.toFixed(decimals) });
+ index++;
+ array.push({ label: "Angular Roll:", value: properties.angularVelocity.z.toFixed(decimals) });
+ index++;
+ array.push({ label: "Angular Damping:", value: properties.angularDamping.toFixed(decimals) });
+ index++;
+
+ array.push({ label: "Gravity X:", value: properties.gravity.x.toFixed(decimals) });
+ index++;
+ array.push({ label: "Gravity Y:", value: properties.gravity.y.toFixed(decimals) });
+ index++;
+ array.push({ label: "Gravity Z:", value: properties.gravity.z.toFixed(decimals) });
+ index++;
+
+ array.push({ label: "Collisions:", type: "header" });
+ index++;
+ array.push({ label: "Mass:", value: properties.mass.toFixed(decimals) });
+ index++;
+ array.push({ label: "Ignore for Collisions:", value: properties.ignoreForCollisions });
+ index++;
+ array.push({ label: "Collisions Will Move:", value: properties.collisionsWillMove });
+ index++;
+
+ array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) });
+ index++;
+
+ array.push({ label: "Visible:", value: properties.visible });
+ index++;
+
+ if (properties.type == "Box" || properties.type == "Sphere") {
+ array.push({ label: "Color:", type: "header" });
+ index++;
+ array.push({ label: "Red:", value: properties.color.red });
+ index++;
+ array.push({ label: "Green:", value: properties.color.green });
+ index++;
+ array.push({ label: "Blue:", value: properties.color.blue });
+ index++;
+ }
+ array.push({ button: "Cancel" });
+ index++;
+
+ editEntityFormArray = array;
+ Window.nonBlockingForm("Edit Properties", array);
+}
+
function handeMenuEvent(menuItem) {
print("menuItemEvent() in JS... menuItem=" + menuItem);
if (menuItem == "Delete") {
@@ -2823,6 +2945,33 @@ function handeMenuEvent(menuItem) {
} else {
print(" Delete Entity.... not holding...");
}
+ } else if (menuItem == "Model List") {
+ var models = new Array();
+ models = Entities.findEntities(MyAvatar.position, Number.MAX_VALUE);
+ for (var i = 0; i < models.length; i++) {
+ models[i].properties = Entities.getEntityProperties(models[i]);
+ models[i].toString = function() {
+ var modelname = decodeURIComponent(
+ this.properties.modelURL.indexOf("/") != -1 ?
+ this.properties.modelURL.substring(this.properties.modelURL.lastIndexOf("/") + 1) :
+ this.properties.modelURL);
+ return "[" + this.properties.type + "] " + modelname;
+ };
+ }
+ var form = [{label: "Model: ", options: models}];
+ form.push({label: "Action: ", options: ["Properties", "Delete", "Teleport"]});
+ form.push({ button: "Cancel" });
+ if (Window.form("Model List", form)) {
+ var selectedModel = form[0].value;
+ if (form[1].value == "Properties") {
+ editModelID = selectedModel;
+ showPropertiesForm();
+ } else if (form[1].value == "Delete") {
+ Entities.deleteEntity(selectedModel);
+ } else if (form[1].value == "Teleport") {
+ MyAvatar.position = selectedModel.properties.position;
+ }
+ }
} else if (menuItem == "Edit Properties...") {
editModelID = -1;
if (leftController.grabbing) {
@@ -2839,126 +2988,7 @@ function handeMenuEvent(menuItem) {
}
if (editModelID != -1) {
print(" Edit Properties.... about to edit properties...");
-
- propertiesForEditedEntity = Entities.getEntityProperties(editModelID);
- var properties = propertiesForEditedEntity;
-
- var array = new Array();
- var index = 0;
- var decimals = 3;
- if (properties.type == "Model") {
- array.push({ label: "Model URL:", value: properties.modelURL });
- index++;
- array.push({ label: "Animation URL:", value: properties.animationURL });
- index++;
- array.push({ label: "Animation is playing:", value: properties.animationIsPlaying });
- index++;
- array.push({ label: "Animation FPS:", value: properties.animationFPS });
- index++;
- array.push({ label: "Animation Frame:", value: properties.animationFrameIndex });
- index++;
- }
- array.push({ label: "Position:", type: "header" });
- index++;
- array.push({ label: "X:", value: properties.position.x.toFixed(decimals) });
- index++;
- array.push({ label: "Y:", value: properties.position.y.toFixed(decimals) });
- index++;
- array.push({ label: "Z:", value: properties.position.z.toFixed(decimals) });
- index++;
-
- array.push({ label: "Registration X:", value: properties.registrationPoint.x.toFixed(decimals) });
- index++;
- array.push({ label: "Registration Y:", value: properties.registrationPoint.y.toFixed(decimals) });
- index++;
- array.push({ label: "Registration Z:", value: properties.registrationPoint.z.toFixed(decimals) });
- index++;
-
- array.push({ label: "Rotation:", type: "header" });
- index++;
- var angles = Quat.safeEulerAngles(properties.rotation);
- array.push({ label: "Pitch:", value: angles.x.toFixed(decimals) });
- index++;
- array.push({ label: "Yaw:", value: angles.y.toFixed(decimals) });
- index++;
- array.push({ label: "Roll:", value: angles.z.toFixed(decimals) });
- index++;
-
- array.push({ label: "Dimensions:", type: "header" });
- index++;
- array.push({ label: "Width:", value: properties.dimensions.x.toFixed(decimals) });
- dimensionX = index;
- index++;
- array.push({ label: "Height:", value: properties.dimensions.y.toFixed(decimals) });
- dimensionY = index;
- index++;
- array.push({ label: "Depth:", value: properties.dimensions.z.toFixed(decimals) });
- dimensionZ = index;
- index++;
- array.push({ label: "", type: "inlineButton", buttonLabel: "Reset to Natural Dimensions", name: "resetDimensions" });
- index++;
- array.push({ label: "Rescale Percentage:", value: 100 });
- rescalePercentage = index;
- index++;
- array.push({ label: "", type: "inlineButton", buttonLabel: "Rescale", name: "rescaleDimensions" });
- index++;
-
- array.push({ label: "Velocity:", type: "header" });
- index++;
- array.push({ label: "Linear X:", value: properties.velocity.x.toFixed(decimals) });
- index++;
- array.push({ label: "Linear Y:", value: properties.velocity.y.toFixed(decimals) });
- index++;
- array.push({ label: "Linear Z:", value: properties.velocity.z.toFixed(decimals) });
- index++;
- array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) });
- index++;
- array.push({ label: "Angular Pitch:", value: properties.angularVelocity.x.toFixed(decimals) });
- index++;
- array.push({ label: "Angular Yaw:", value: properties.angularVelocity.y.toFixed(decimals) });
- index++;
- array.push({ label: "Angular Roll:", value: properties.angularVelocity.z.toFixed(decimals) });
- index++;
- array.push({ label: "Angular Damping:", value: properties.angularDamping.toFixed(decimals) });
- index++;
-
- array.push({ label: "Gravity X:", value: properties.gravity.x.toFixed(decimals) });
- index++;
- array.push({ label: "Gravity Y:", value: properties.gravity.y.toFixed(decimals) });
- index++;
- array.push({ label: "Gravity Z:", value: properties.gravity.z.toFixed(decimals) });
- index++;
-
- array.push({ label: "Collisions:", type: "header" });
- index++;
- array.push({ label: "Mass:", value: properties.mass.toFixed(decimals) });
- index++;
- array.push({ label: "Ignore for Collisions:", value: properties.ignoreForCollisions });
- index++;
- array.push({ label: "Collisions Will Move:", value: properties.collisionsWillMove });
- index++;
-
- array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) });
- index++;
-
- array.push({ label: "Visible:", value: properties.visible });
- index++;
-
- if (properties.type == "Box" || properties.type == "Sphere") {
- array.push({ label: "Color:", type: "header" });
- index++;
- array.push({ label: "Red:", value: properties.color.red });
- index++;
- array.push({ label: "Green:", value: properties.color.green });
- index++;
- array.push({ label: "Blue:", value: properties.color.blue });
- index++;
- }
- array.push({ button: "Cancel" });
- index++;
-
- editEntityFormArray = array;
- Window.nonBlockingForm("Edit Properties", array);
+ showPropertiesForm(editModelID);
}
} else if (menuItem == "Paste Models") {
modelImporter.paste();
diff --git a/examples/headMove.js b/examples/headMove.js
index 894575ead1..086ef80abc 100644
--- a/examples/headMove.js
+++ b/examples/headMove.js
@@ -43,7 +43,7 @@ var noFly = true;
var fixedWalkVelocity = true;
//var roomLimits = { xMin: 618, xMax: 635.5, zMin: 528, zMax: 552.5 };
-var roomLimits = { xMin: 100.0, xMax: 206.5, zMin: 251.4, zMax: 269.5 };
+var roomLimits = { xMin: -1.0, xMax: -1.0, zMin: -1.0, zMax: -1.0 };
function isInRoom(position) {
var BUFFER = 2.0;
diff --git a/examples/leapHands.js b/examples/leapHands.js
index 95d3969a08..cc50a328f8 100644
--- a/examples/leapHands.js
+++ b/examples/leapHands.js
@@ -13,7 +13,10 @@
var leapHands = (function () {
- var hands,
+ var isOnHMD,
+ LEAP_OFFSET = 0.019, // Thickness of Leap Motion plus HMD clip
+ HMD_OFFSET = 0.100, // Eyeballs to front surface of Oculus DK2 TODO: Confirm and make depend on device and eye relief
+ hands,
wrists,
NUM_HANDS = 2, // 0 = left; 1 = right
fingers,
@@ -188,8 +191,6 @@ var leapHands = (function () {
function setUp() {
- calibrationStatus = UNCALIBRATED;
-
// TODO: Leap Motion controller joint naming doesn't match up with skeleton joint naming; numbers are out by 1.
hands = [
@@ -265,6 +266,20 @@ var leapHands = (function () {
{ jointName: "RightHandPinky3", controller: Controller.createInputController("Spatial", "joint_R_pinky4") }
]
];
+
+ isOnHMD = Menu.isOptionChecked("Leap Motion on HMD");
+ if (isOnHMD) {
+ print("Leap Motion is on HMD");
+
+ // Offset of Leap Motion origin from physical eye position
+ hands[0].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET };
+ hands[1].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET };
+
+ calibrationStatus = CALIBRATED;
+ } else {
+ print("Leap Motion is on desk");
+ calibrationStatus = UNCALIBRATED;
+ }
}
function moveHands() {
@@ -278,7 +293,9 @@ var leapHands = (function () {
handYaw,
handRotation,
wristAbsRotation,
- locRotation;
+ locRotation,
+ cameraOrientation,
+ inverseAvatarOrientation;
for (h = 0; h < NUM_HANDS; h += 1) {
side = h === 0 ? -1.0 : 1.0;
@@ -291,35 +308,82 @@ var leapHands = (function () {
}
// Hand position ...
- handOffset = hands[h].controller.getAbsTranslation();
- handOffset = {
- x: -handOffset.x,
- y: hands[h].zeroPosition.y + handOffset.y,
- z: hands[h].zeroPosition.z - handOffset.z
- };
+ if (isOnHMD) {
- // TODO: 2.0* scale factor should not be necessary; Leap Motion controller code needs investigating.
- handRoll = 2.0 * -hands[h].controller.getAbsRotation().z;
- wristAbsRotation = wrists[h].controller.getAbsRotation();
- handPitch = 2.0 * -wristAbsRotation.x;
- handYaw = 2.0 * wristAbsRotation.y;
+ // Hand offset in camera coordinates ...
+ handOffset = hands[h].controller.getAbsTranslation();
+ handOffset = {
+ x: hands[h].zeroPosition.x - handOffset.x,
+ y: hands[h].zeroPosition.y - handOffset.z,
+ z: hands[h].zeroPosition.z + handOffset.y
+ };
+ handOffset.z = -handOffset.z;
- // TODO: Leap Motion controller's right-hand roll calculation only works if physical hand is upside down.
- // Approximate fix is to add a fudge factor.
- if (h === 1 && isWindows) {
- handRoll = handRoll + 0.6 * PI;
- }
+ // Hand offset in world coordinates ...
+ cameraOrientation = Camera.getOrientation();
+ handOffset = Vec3.sum(Camera.getPosition(), Vec3.multiplyQbyV(cameraOrientation, handOffset));
- // Hand position and orientation ...
- if (h === 0) {
- handRotation = Quat.multiply(Quat.angleAxis(-90.0, { x: 0, y: 1, z: 0 }),
- Quat.fromVec3Radians({ x: handRoll, y: handYaw, z: -handPitch }));
+ // Hand offset in avatar coordinates ...
+ inverseAvatarOrientation = Quat.inverse(MyAvatar.orientation);
+ handOffset = Vec3.subtract(handOffset, MyAvatar.position);
+ handOffset = Vec3.multiplyQbyV(inverseAvatarOrientation, handOffset);
+ handOffset.z = -handOffset.z;
+ handOffset.x = -handOffset.x;
+ // Hand rotation in camera coordinates ...
+ // TODO: 2.0* scale factors should not be necessary; Leap Motion controller code needs investigating.
+ handRoll = 2.0 * -hands[h].controller.getAbsRotation().z;
+ wristAbsRotation = wrists[h].controller.getAbsRotation();
+ handPitch = 2.0 * wristAbsRotation.x - PI / 2.0;
+ handYaw = 2.0 * -wristAbsRotation.y;
+ // TODO: Roll values only work if hand is upside down; Leap Motion controller code needs investigating.
+ handRoll = PI + handRoll;
+
+ if (h === 0) {
+ handRotation = Quat.multiply(Quat.angleAxis(-90.0, { x: 0, y: 1, z: 0 }),
+ Quat.fromVec3Radians({ x: handRoll, y: handYaw, z: -handPitch }));
+ } else {
+ handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 0, y: 1, z: 0 }),
+ Quat.fromVec3Radians({ x: -handRoll, y: handYaw, z: handPitch }));
+ }
+
+ // Hand rotation in avatar coordinates ...
+ cameraOrientation.x = -cameraOrientation.x;
+ cameraOrientation.z = -cameraOrientation.z;
+ handRotation = Quat.multiply(cameraOrientation, handRotation);
+ handRotation = Quat.multiply(inverseAvatarOrientation, handRotation);
} else {
- handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 0, y: 1, z: 0 }),
- Quat.fromVec3Radians({ x: -handRoll, y: handYaw, z: handPitch }));
+
+ handOffset = hands[h].controller.getAbsTranslation();
+ handOffset = {
+ x: -handOffset.x,
+ y: hands[h].zeroPosition.y + handOffset.y,
+ z: hands[h].zeroPosition.z - handOffset.z
+ };
+
+ // TODO: 2.0* scale factors should not be necessary; Leap Motion controller code needs investigating.
+ handRoll = 2.0 * -hands[h].controller.getAbsRotation().z;
+ wristAbsRotation = wrists[h].controller.getAbsRotation();
+ handPitch = 2.0 * -wristAbsRotation.x;
+ handYaw = 2.0 * wristAbsRotation.y;
+
+ // TODO: Leap Motion controller's right-hand roll calculation only works if physical hand is upside down.
+ // Approximate fix is to add a fudge factor.
+ if (h === 1 && isWindows) {
+ handRoll = handRoll + 0.6 * PI;
+ }
+
+ // Hand position and orientation ...
+ if (h === 0) {
+ handRotation = Quat.multiply(Quat.angleAxis(-90.0, { x: 0, y: 1, z: 0 }),
+ Quat.fromVec3Radians({ x: handRoll, y: handYaw, z: -handPitch }));
+ } else {
+ handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 0, y: 1, z: 0 }),
+ Quat.fromVec3Radians({ x: -handRoll, y: handYaw, z: handPitch }));
+ }
}
+
MyAvatar.setJointModelPositionAndOrientation(hands[h].jointName, handOffset, handRotation, true);
// Finger joints ...
diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt
index a2a832afe0..ddc034801b 100644
--- a/interface/CMakeLists.txt
+++ b/interface/CMakeLists.txt
@@ -137,8 +137,9 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
set(${${EXTERNAL}_UPPERCASE}_LIBRARIES ${${${EXTERNAL}_UPPERCASE}_LIBRARY})
endif ()
- target_link_libraries(${TARGET_NAME} ${${${EXTERNAL}_UPPERCASE}_LIBRARIES})
-
+ if (NOT APPLE OR NOT ${${EXTERNAL}_UPPERCASE} MATCHES "SIXENSE")
+ target_link_libraries(${TARGET_NAME} ${${${EXTERNAL}_UPPERCASE}_LIBRARIES})
+ endif ()
endif ()
endforeach()
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 0fd7ae96d1..ec40056299 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -182,7 +182,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_lastNackTime(usecTimestampNow()),
_lastSendDownstreamAudioStats(usecTimestampNow())
{
-
// read the ApplicationInfo.ini file for Name/Version/Domain information
QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat);
@@ -303,9 +302,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// connect to the domainChangeRequired signal on AddressManager
connect(&addressManager, &AddressManager::possibleDomainChangeRequired,
this, &Application::changeDomainHostname);
-
- // when -url in command line, teleport to location
- addressManager.handleLookupString(getCmdOption(argc, constArgv, "-url"));
_settings = new QSettings(this);
_numChangedSettings = 0;
@@ -381,12 +377,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_particleEditSender.setPacketsPerSecond(3000); // super high!!
_entityEditSender.setPacketsPerSecond(3000); // super high!!
- // Set the sixense filtering
- _sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense));
-
- // Set hand controller velocity filtering
- _sixenseManager.setLowVelocityFilter(Menu::getInstance()->isOptionChecked(MenuOption::LowVelocityFilter));
-
checkVersion();
_overlays.init(_glWidget); // do this before scripts load
@@ -609,8 +599,11 @@ void Application::paintGL() {
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
_myCamera.setTightness(0.0f); // In first person, camera follows (untweaked) head exactly without delay
- _myCamera.setTargetPosition(_myAvatar->getHead()->getEyePosition());
- _myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
+ if (!OculusManager::isConnected()) {
+ _myCamera.setTargetPosition(_myAvatar->getHead()->getEyePosition());
+ _myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
+ }
+ // OculusManager::display() updates camera position and rotation a bit further on.
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
//Note, the camera distance is set in Camera::setMode() so we dont have to do it here.
@@ -640,7 +633,9 @@ void Application::paintGL() {
}
// Update camera position
- _myCamera.update( 1.f/_fps );
+ if (!OculusManager::isConnected()) {
+ _myCamera.update(1.f / _fps);
+ }
// Note: whichCamera is used to pick between the normal camera myCamera for our
// main camera, vs, an alternate camera. The alternate camera we support right now
@@ -650,7 +645,7 @@ void Application::paintGL() {
// Why have two cameras? Well, one reason is that because in the case of the renderViewFrustum()
// code, we want to keep the state of "myCamera" intact, so we can render what the view frustum of
// myCamera is. But we also want to do meaningful camera transforms on OpenGL for the offset camera
- Camera whichCamera = _myCamera;
+ Camera* whichCamera = &_myCamera;
if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayFrustum)) {
@@ -664,7 +659,7 @@ void Application::paintGL() {
_viewFrustumOffsetCamera.setDistance(viewFrustumOffset.distance);
_viewFrustumOffsetCamera.initialize(); // force immediate snap to ideal position and orientation
_viewFrustumOffsetCamera.update(1.f/_fps);
- whichCamera = _viewFrustumOffsetCamera;
+ whichCamera = &_viewFrustumOffsetCamera;
}
if (Menu::getInstance()->getShadowsEnabled()) {
@@ -677,15 +672,16 @@ void Application::paintGL() {
glClear(GL_COLOR_BUFFER_BIT);
//When in mirror mode, use camera rotation. Otherwise, use body rotation
- if (whichCamera.getMode() == CAMERA_MODE_MIRROR) {
- OculusManager::display(whichCamera.getRotation(), whichCamera.getPosition(), whichCamera);
+ if (whichCamera->getMode() == CAMERA_MODE_MIRROR) {
+ OculusManager::display(whichCamera->getRotation(), whichCamera->getPosition(), *whichCamera);
} else {
- OculusManager::display(_myAvatar->getWorldAlignedOrientation(), _myAvatar->getDefaultEyePosition(), whichCamera);
+ OculusManager::display(_myAvatar->getWorldAlignedOrientation(), _myAvatar->getDefaultEyePosition(), *whichCamera);
}
+ _myCamera.update(1.f / _fps);
} else if (TV3DManager::isConnected()) {
- TV3DManager::display(whichCamera);
+ TV3DManager::display(*whichCamera);
} else {
_glowEffect.prepare();
@@ -693,7 +689,7 @@ void Application::paintGL() {
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
- displaySide(whichCamera);
+ displaySide(*whichCamera);
glPopMatrix();
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
@@ -1484,7 +1480,7 @@ void Application::setRenderVoxels(bool voxelRender) {
}
void Application::setLowVelocityFilter(bool lowVelocityFilter) {
- getSixenseManager()->setLowVelocityFilter(lowVelocityFilter);
+ SixenseManager::getInstance().setLowVelocityFilter(lowVelocityFilter);
}
void Application::doKillLocalVoxels() {
@@ -1797,7 +1793,25 @@ void Application::init() {
Menu::getInstance()->loadSettings();
_audio.setReceivedAudioStreamSettings(Menu::getInstance()->getReceivedAudioStreamSettings());
- qDebug("Loaded settings");
+ qDebug() << "Loaded settings";
+
+ // when --url in command line, teleport to location
+ const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
+ int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
+ if (urlIndex != -1) {
+ AddressManager::getInstance().handleLookupString(arguments().value(urlIndex + 1));
+ }
+
+#ifdef __APPLE__
+ if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseEnabled)) {
+ // on OS X we only setup sixense if the user wants it on - this allows running without the hid_init crash
+ // if hydra support is temporarily not required
+ Menu::getInstance()->toggleSixense(true);
+ }
+#else
+ // setup sixense
+ Menu::getInstance()->toggleSixense(true);
+#endif
// initialize our face trackers after loading the menu settings
_faceshift.init();
@@ -2174,7 +2188,7 @@ void Application::update(float deltaTime) {
DeviceTracker::updateAll();
updateFaceshift();
updateVisage();
- _sixenseManager.update(deltaTime);
+ SixenseManager::getInstance().update(deltaTime);
JoystickScriptingInterface::getInstance().update();
_prioVR.update(deltaTime);
diff --git a/interface/src/Application.h b/interface/src/Application.h
index f8710bae7b..e50039a20e 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -219,7 +219,6 @@ public:
DdeFaceTracker* getDDE() { return &_dde; }
CaraFaceTracker* getCara() { return &_cara; }
FaceTracker* getActiveFaceTracker();
- SixenseManager* getSixenseManager() { return &_sixenseManager; }
PrioVR* getPrioVR() { return &_prioVR; }
BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; }
QUndoStack* getUndoStack() { return &_undoStack; }
@@ -510,7 +509,6 @@ private:
CaraFaceTracker _cara;
DdeFaceTracker _dde;
- SixenseManager _sixenseManager;
PrioVR _prioVR;
Camera _myCamera; // My view onto the world
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 8f7b2be43b..94af1ad80a 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -431,11 +431,18 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false);
QMenu* sixenseOptionsMenu = handOptionsMenu->addMenu("Sixense");
+#ifdef __APPLE__
+ addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu,
+ MenuOption::SixenseEnabled,
+ 0, false,
+ this,
+ SLOT(toggleSixense(bool)));
+#endif
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu,
MenuOption::FilterSixense,
0,
true,
- appInstance->getSixenseManager(),
+ &SixenseManager::getInstance(),
SLOT(setFilter(bool)));
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu,
MenuOption::LowVelocityFilter,
@@ -446,6 +453,9 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseMouseInput, 0, true);
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseLasers, 0, false);
+ QMenu* leapOptionsMenu = handOptionsMenu->addMenu("Leap Motion");
+ addCheckableActionToQMenuAndActionHash(leapOptionsMenu, MenuOption::LeapMotionOnHMD, 0, false);
+
QMenu* networkMenu = developerMenu->addMenu("Network");
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false);
addCheckableActionToQMenuAndActionHash(networkMenu,
@@ -1134,6 +1144,18 @@ void Menu::editAnimations() {
}
}
+void Menu::toggleSixense(bool shouldEnable) {
+ SixenseManager& sixenseManager = SixenseManager::getInstance();
+
+ if (shouldEnable && !sixenseManager.isInitialized()) {
+ sixenseManager.initialize();
+ sixenseManager.setFilter(isOptionChecked(MenuOption::FilterSixense));
+ sixenseManager.setLowVelocityFilter(isOptionChecked(MenuOption::LowVelocityFilter));
+ }
+
+ sixenseManager.setIsEnabled(shouldEnable);
+}
+
void Menu::changePrivateKey() {
// setup the dialog
QInputDialog privateKeyDialog(Application::getInstance()->getWindow());
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index b267ab8b2c..2402090213 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -186,6 +186,7 @@ public slots:
void pasteToVoxel();
void toggleLoginMenuItem();
+ void toggleSixense(bool shouldEnable);
QMenu* addMenu(const QString& menuName);
void removeMenu(const QString& menuName);
@@ -402,6 +403,7 @@ namespace MenuOption {
const QString IncreaseAvatarSize = "Increase Avatar Size";
const QString IncreaseVoxelSize = "Increase Voxel Size";
const QString KeyboardMotorControl = "Enable Keyboard Motor Control";
+ const QString LeapMotionOnHMD = "Leap Motion on HMD";
const QString LoadScript = "Open and Run Script File...";
const QString LoadScriptURL = "Open and Run Script from URL...";
const QString LodTools = "LOD Tools";
@@ -448,6 +450,7 @@ namespace MenuOption {
const QString ShowBordersVoxelNodes = "Show Voxel Nodes";
const QString ShowIKConstraints = "Show IK Constraints";
const QString SimpleShadows = "Simple";
+ const QString SixenseEnabled = "Enable Hydra Support";
const QString SixenseMouseInput = "Enable Sixense Mouse Input";
const QString SixenseLasers = "Enable Sixense UI Lasers";
const QString StandOnNearbyFloors = "Stand on nearby floors";
diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp
index b993d5243e..ae82e57017 100644
--- a/interface/src/MetavoxelSystem.cpp
+++ b/interface/src/MetavoxelSystem.cpp
@@ -1204,7 +1204,8 @@ int PointAugmentVisitor::visit(MetavoxelInfo& info) {
_points.swap(swapPoints);
buffer = new PointBuffer(swapPoints);
}
- info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer)));
+ BufferDataPointer pointer(buffer);
+ info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer));
}
return STOP_RECURSION;
}
@@ -1219,7 +1220,8 @@ bool PointAugmentVisitor::postVisit(MetavoxelInfo& info) {
_points.swap(swapPoints);
buffer = new PointBuffer(swapPoints);
}
- info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer)));
+ BufferDataPointer pointer(buffer);
+ info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer));
return true;
}
@@ -1446,7 +1448,8 @@ int HeightfieldRegionVisitor::visit(MetavoxelInfo& info) {
_data->guide(_fetchVisitor);
}
}
- info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer)));
+ BufferDataPointer pointer(buffer);
+ info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer));
return STOP_RECURSION;
}
@@ -1505,7 +1508,8 @@ int HeightfieldUpdateVisitor::visit(MetavoxelInfo& info) {
buffer->getHeight(), buffer->getColor(), buffer->getMaterial(), buffer->getMaterials());
_fetchVisitor.init(newBuffer);
_data->guide(_fetchVisitor);
- info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(newBuffer)));
+ BufferDataPointer pointer(newBuffer);
+ info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer));
return STOP_RECURSION;
}
@@ -1878,114 +1882,42 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
glm::mat3 atrans = glm::transpose(a);
glm::mat3 ata = atrans * a;
- // compute the SVD of ata; first, find the eigenvalues
- // (see http://en.wikipedia.org/wiki/Eigenvalue_algorithm#3.C3.973_matrices)
- glm::vec3 eigenvalues;
- float p1 = ata[0][1] * ata[0][1] + ata[0][2] * ata[0][2] + ata[1][2] * ata[1][2];
- if (p1 < EPSILON) {
- eigenvalues = glm::vec3(ata[0][0], ata[1][1], ata[2][2]);
- if (eigenvalues[2] < eigenvalues[1]) {
- qSwap(eigenvalues[2], eigenvalues[1]);
+ // find the eigenvalues and eigenvectors of ata
+ // (see http://en.wikipedia.org/wiki/Jacobi_eigenvalue_algorithm)
+ glm::mat3 d = ata;
+ glm::quat combinedRotation;
+ const int MAX_ITERATIONS = 20;
+ for (int i = 0; i < MAX_ITERATIONS; i++) {
+ glm::vec3 offDiagonals = glm::abs(glm::vec3(d[1][0], d[2][0], d[2][1]));
+ int largestIndex = (offDiagonals[0] > offDiagonals[1]) ? (offDiagonals[0] > offDiagonals[2] ? 0 : 2) :
+ (offDiagonals[1] > offDiagonals[2] ? 1 : 2);
+ const float DESIRED_PRECISION = 0.00001f;
+ if (offDiagonals[largestIndex] < DESIRED_PRECISION) {
+ break;
}
- if (eigenvalues[1] < eigenvalues[0]) {
- qSwap(eigenvalues[1], eigenvalues[0]);
+ int largestJ = (largestIndex == 2) ? 1 : 0;
+ int largestI = (largestIndex == 0) ? 1 : 2;
+ float sjj = d[largestJ][largestJ];
+ float sii = d[largestI][largestI];
+ float angle = (sii == sjj ? PI_OVER_TWO : glm::atan(2.0f * d[largestJ][largestI], sjj - sii)) / 2.0f;
+ glm::quat rotation = glm::angleAxis(angle, largestIndex == 0 ? glm::vec3(0.0f, 0.0f, -1.0f) :
+ (largestIndex == 1 ? glm::vec3(0.0f, 1.0f, 0.0f) : glm::vec3(-1.0f, 0.0f, 0.0f)));
+ if (rotation.w == 0.0f) {
+ break;
}
- if (eigenvalues[2] < eigenvalues[1]) {
- qSwap(eigenvalues[2], eigenvalues[1]);
- }
- } else {
- float q = (ata[0][0] + ata[1][1] + ata[2][2]) / 3.0f;
- float d1 = ata[0][0] - q, d2 = ata[1][1] - q, d3 = ata[2][2] - q;
- float p2 = d1 * d1 + d2 * d2 + d3 * d3 + 2.0f * p1;
- float p = glm::sqrt(p2 / 6.0f);
- glm::mat3 b = (ata - glm::mat3(q)) / p;
- float r = glm::determinant(b) / 2.0f;
- float phi;
- if (r <= -1.0f) {
- phi = PI / 3.0f;
- } else if (r >= 1.0f) {
- phi = 0.0f;
- } else {
- phi = glm::acos(r) / 3.0f;
- }
- eigenvalues[2] = q + 2.0f * p * glm::cos(phi);
- eigenvalues[0] = q + 2.0f * p * glm::cos(phi + (2.0f * PI / 3.0f));
- eigenvalues[1] = 3.0f * q - eigenvalues[0] - eigenvalues[2];
+ combinedRotation = glm::normalize(rotation * combinedRotation);
+ glm::mat3 matrix = glm::mat3_cast(combinedRotation);
+ d = matrix * ata * glm::transpose(matrix);
}
// form the singular matrix from the eigenvalues
- glm::mat3 d;
const float MIN_SINGULAR_THRESHOLD = 0.1f;
- d[0][0] = (eigenvalues[0] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / eigenvalues[0];
- d[1][1] = (eigenvalues[1] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / eigenvalues[1];
- d[2][2] = (eigenvalues[2] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / eigenvalues[2];
-
- glm::mat3 m[] = { ata - glm::mat3(eigenvalues[0]), ata - glm::mat3(eigenvalues[1]),
- ata - glm::mat3(eigenvalues[2]) };
-
- // form the orthogonal matrix from the eigenvectors
- // see http://www.geometrictools.com/Documentation/EigenSymmetric3x3.pdf
- bool same01 = glm::abs(eigenvalues[0] - eigenvalues[1]) < EPSILON;
- bool same12 = glm::abs(eigenvalues[1] - eigenvalues[2]) < EPSILON;
- glm::mat3 u;
- if (!(same01 && same12)) {
- if (same01 || same12) {
- int i = same01 ? 2 : 0;
- for (int j = 0; j < 3; j++) {
- glm::vec3 first = glm::vec3(m[i][0][j], m[i][1][j], m[i][2][j]);
- int j2 = (j + 1) % 3;
- glm::vec3 second = glm::vec3(m[i][0][j2], m[i][1][j2], m[i][2][j2]);
- glm::vec3 cross = glm::cross(first, second);
- float length = glm::length(cross);
- if (length > EPSILON) {
- u[0][i] = cross[0] / length;
- u[1][i] = cross[1] / length;
- u[2][i] = cross[2] / length;
- break;
- }
- }
- i = (i + 1) % 3;
- for (int j = 0; j < 3; j++) {
- glm::vec3 first = glm::vec3(m[i][0][j], m[i][1][j], m[i][2][j]);
- float length = glm::length(first);
- if (length > EPSILON) {
- glm::vec3 second = glm::cross(first, glm::vec3(1.0f, 0.0f, 0.0f));
- length = glm::length(second);
- if (length < EPSILON) {
- second = glm::cross(first, glm::vec3(0.0f, 1.0f, 0.0f));
- length = glm::length(second);
- }
- u[0][i] = second[0] / length;
- u[1][i] = second[1] / length;
- u[2][i] = second[2] / length;
- second = glm::normalize(glm::cross(second, first));
- i = (i + 1) % 3;
- u[0][i] = second[0];
- u[1][i] = second[1];
- u[2][i] = second[2];
- break;
- }
- }
- } else {
- for (int i = 0; i < 3; i++) {
- for (int j = 0; j < 3; j++) {
- glm::vec3 first = glm::vec3(m[i][0][j], m[i][1][j], m[i][2][j]);
- int j2 = (j + 1) % 3;
- glm::vec3 second = glm::vec3(m[i][0][j2], m[i][1][j2], m[i][2][j2]);
- glm::vec3 cross = glm::cross(first, second);
- float length = glm::length(cross);
- if (length > EPSILON) {
- u[0][i] = cross[0] / length;
- u[1][i] = cross[1] / length;
- u[2][i] = cross[2] / length;
- break;
- }
- }
- }
- }
- }
+ d[0][0] = (d[0][0] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / d[0][0];
+ d[1][1] = (d[1][1] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / d[1][1];
+ d[2][2] = (d[2][2] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / d[2][2];
// compute the pseudo-inverse, ataplus, and use to find the minimizing solution
+ glm::mat3 u = glm::mat3_cast(combinedRotation);
glm::mat3 ataplus = glm::transpose(u) * d * u;
glm::vec3 solution = (ataplus * atrans * b) + center;
@@ -2078,7 +2010,8 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
buffer = new VoxelBuffer(vertices, indices, material->getMaterials());
}
- info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer)));
+ BufferDataPointer pointer(buffer);
+ info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer));
return STOP_RECURSION;
}
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index c82eaa2bac..2b72fe2c23 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -520,20 +520,18 @@ void Avatar::renderBody(RenderMode renderMode, bool postLighting, float glowLeve
renderAttachments(renderMode);
}
}
- if (!postLighting) {
- getHead()->render(1.0f, modelRenderMode);
+ getHead()->render(1.0f, modelRenderMode, postLighting);
- if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
- // Render Hair
- glPushMatrix();
- glm::vec3 headPosition = getHead()->getPosition();
- glTranslatef(headPosition.x, headPosition.y, headPosition.z);
- const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
- glm::vec3 axis = glm::axis(rotation);
- glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
- _hair.render();
- glPopMatrix();
- }
+ if (!postLighting && Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
+ // Render Hair
+ glPushMatrix();
+ glm::vec3 headPosition = getHead()->getPosition();
+ glTranslatef(headPosition.x, headPosition.y, headPosition.z);
+ const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
+ glm::vec3 axis = glm::axis(rotation);
+ glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
+ _hair.render();
+ glPopMatrix();
}
}
diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp
index f67a131221..bc557bdb57 100644
--- a/interface/src/avatar/Head.cpp
+++ b/interface/src/avatar/Head.cpp
@@ -211,17 +211,16 @@ void Head::relaxLean(float deltaTime) {
_deltaLeanForward *= relaxationFactor;
}
-void Head::render(float alpha, Model::RenderMode mode) {
- _faceModel.render(alpha, mode);
- if (_renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) {
- Application::getInstance()->getDeferredLightingEffect()->addPostLightingRenderable(this);
+void Head::render(float alpha, Model::RenderMode mode, bool postLighting) {
+ if (postLighting) {
+ if (_renderLookatVectors) {
+ renderLookatVectors(_leftEyePosition, _rightEyePosition, getCorrectedLookAtPosition());
+ }
+ } else {
+ _faceModel.render(alpha, mode);
}
}
-void Head::renderPostLighting() {
- renderLookatVectors(_leftEyePosition, _rightEyePosition, getCorrectedLookAtPosition());
-}
-
void Head::setScale (float scale) {
if (_scale == scale) {
return;
diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h
index efcee9ed8d..bc4142eab0 100644
--- a/interface/src/avatar/Head.h
+++ b/interface/src/avatar/Head.h
@@ -23,7 +23,6 @@
#include "FaceModel.h"
#include "InterfaceConfig.h"
#include "world.h"
-#include "renderer/DeferredLightingEffect.h"
enum eyeContactTargets {
LEFT_EYE,
@@ -36,15 +35,14 @@ const float EYE_EAR_GAP = 0.08f;
class Avatar;
class ProgramObject;
-class Head : public HeadData, public PostLightingRenderable {
+class Head : public HeadData {
public:
Head(Avatar* owningAvatar);
void init();
void reset();
void simulate(float deltaTime, bool isMine, bool billboard = false);
- void render(float alpha, Model::RenderMode mode);
- virtual void renderPostLighting();
+ void render(float alpha, Model::RenderMode mode, bool postLighting);
void setScale(float scale);
void setPosition(glm::vec3 position) { _position = position; }
void setAverageLoudness(float averageLoudness) { _averageLoudness = averageLoudness; }
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 5cd8ba2052..f7aa8a2bd6 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -1147,20 +1147,18 @@ void MyAvatar::renderBody(RenderMode renderMode, bool postLighting, float glowLe
const Camera *camera = Application::getInstance()->getCamera();
const glm::vec3 cameraPos = camera->getPosition() + (camera->getRotation() * glm::vec3(0.0f, 0.0f, 1.0f)) * camera->getDistance();
if (shouldRenderHead(cameraPos, renderMode)) {
- if (!postLighting) {
- getHead()->render(1.0f, modelRenderMode);
+ getHead()->render(1.0f, modelRenderMode, postLighting);
- if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
- // Render Hair
- glPushMatrix();
- glm::vec3 headPosition = getHead()->getPosition();
- glTranslatef(headPosition.x, headPosition.y, headPosition.z);
- const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
- glm::vec3 axis = glm::axis(rotation);
- glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
- _hair.render();
- glPopMatrix();
- }
+ if (!postLighting && Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
+ // Render Hair
+ glPushMatrix();
+ glm::vec3 headPosition = getHead()->getPosition();
+ glTranslatef(headPosition.x, headPosition.y, headPosition.z);
+ const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
+ glm::vec3 axis = glm::axis(rotation);
+ glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
+ _hair.render();
+ glPopMatrix();
}
}
if (postLighting) {
diff --git a/interface/src/devices/Leapmotion.cpp b/interface/src/devices/Leapmotion.cpp
index 7060c5c5e9..a3794123ce 100644
--- a/interface/src/devices/Leapmotion.cpp
+++ b/interface/src/devices/Leapmotion.cpp
@@ -8,8 +8,9 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-#include "SharedUtil.h"
#include "Leapmotion.h"
+#include "Menu.h"
+#include "SharedUtil.h"
const int PALMROOT_NUM_JOINTS = 3;
const int FINGER_NUM_JOINTS = 4;
@@ -101,6 +102,12 @@ Leapmotion::Leapmotion() :
}
}
}
+
+#ifdef HAVE_LEAPMOTION
+ if (Menu::getInstance()->isOptionChecked(MenuOption::LeapMotionOnHMD)) {
+ _controller.setPolicyFlags(Leap::Controller::POLICY_OPTIMIZE_HMD);
+ }
+#endif
}
Leapmotion::~Leapmotion() {
diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp
index 27590d9a72..65518b839c 100644
--- a/interface/src/devices/OculusManager.cpp
+++ b/interface/src/devices/OculusManager.cpp
@@ -412,6 +412,10 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p
glBindTexture(GL_TEXTURE_2D, 0);
+ // Update camera for use by rest of Interface.
+ whichCamera.setTargetPosition((_leftEyePosition + _rightEyePosition) / 2.f);
+ whichCamera.setTargetRotation(_camera->getTargetRotation());
+
#endif
}
diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp
index 417b0619f8..c3d694d06c 100644
--- a/interface/src/devices/SixenseManager.cpp
+++ b/interface/src/devices/SixenseManager.cpp
@@ -30,23 +30,28 @@ const int CALIBRATION_STATE_COMPLETE = 4;
const float NECK_X = 0.25f; // meters
const float NECK_Y = 0.3f; // meters
const float NECK_Z = 0.3f; // meters
+
+#ifdef __APPLE__
+typedef int (*SixenseBaseFunction)();
+typedef int (*SixenseTakeIntFunction)(int);
+typedef int (*SixenseTakeIntAndSixenseControllerData)(int, sixenseControllerData*);
#endif
-SixenseManager::SixenseManager() {
-#ifdef HAVE_SIXENSE
- _lastMovement = 0;
- _amountMoved = glm::vec3(0.0f);
- _lowVelocityFilter = false;
-
- _calibrationState = CALIBRATION_STATE_IDLE;
- // By default we assume the _neckBase (in orb frame) is as high above the orb
- // as the "torso" is below it.
- _neckBase = glm::vec3(NECK_X, -NECK_Y, NECK_Z);
-
- sixenseInit();
-
#endif
- _hydrasConnected = false;
+
+SixenseManager& SixenseManager::getInstance() {
+ static SixenseManager sharedInstance;
+ return sharedInstance;
+}
+
+SixenseManager::SixenseManager() :
+#if defined(HAVE_SIXENSE) && defined(__APPLE__)
+ _sixenseLibrary(NULL),
+#endif
+ _isInitialized(false),
+ _isEnabled(true),
+ _hydrasConnected(false)
+{
_triggerPressed[0] = false;
_bumperPressed[0] = false;
_oldX[0] = -1;
@@ -58,155 +63,229 @@ SixenseManager::SixenseManager() {
}
SixenseManager::~SixenseManager() {
+#ifdef HAVE_SIXENSE_
+
+ if (_isInitialized) {
+#ifdef __APPLE__
+ SixenseBaseFunction sixenseExit = (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseExit");
+#endif
+
+ sixenseExit();
+ }
+
+#ifdef __APPLE__
+ delete _sixenseLibrary;
+#endif
+
+#endif
+}
+
+void SixenseManager::initialize() {
#ifdef HAVE_SIXENSE
- sixenseExit();
+
+ if (!_isInitialized) {
+ _lastMovement = 0;
+ _amountMoved = glm::vec3(0.0f);
+ _lowVelocityFilter = false;
+
+ _calibrationState = CALIBRATION_STATE_IDLE;
+ // By default we assume the _neckBase (in orb frame) is as high above the orb
+ // as the "torso" is below it.
+ _neckBase = glm::vec3(NECK_X, -NECK_Y, NECK_Z);
+
+#ifdef __APPLE__
+
+ if (!_sixenseLibrary) {
+ const QString SIXENSE_LIBRARY_NAME = "libsixense_x64.dylib";
+ _sixenseLibrary = new QLibrary(SIXENSE_LIBRARY_NAME);
+ }
+
+ qDebug() << "Initializing sixense library for hydra support - libsixense_x64.dylib load state is"
+ << _sixenseLibrary->isLoaded();
+ SixenseBaseFunction sixenseInit = (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseInit");
+#endif
+
+ sixenseInit();
+
+ _isInitialized = true;
+ }
+
#endif
}
void SixenseManager::setFilter(bool filter) {
#ifdef HAVE_SIXENSE
- if (filter) {
- sixenseSetFilterEnabled(1);
- } else {
- sixenseSetFilterEnabled(0);
+
+ if (_isInitialized) {
+#ifdef __APPLE__
+ SixenseTakeIntFunction sixenseSetFilterEnabled = (SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseSetFilterEnabled");
+#endif
+
+ if (filter) {
+ sixenseSetFilterEnabled(1);
+ } else {
+ sixenseSetFilterEnabled(0);
+ }
}
+
#endif
}
void SixenseManager::update(float deltaTime) {
#ifdef HAVE_SIXENSE
- // if the controllers haven't been moved in a while, disable
- const unsigned int MOVEMENT_DISABLE_SECONDS = 3;
- if (usecTimestampNow() - _lastMovement > (MOVEMENT_DISABLE_SECONDS * USECS_PER_SECOND)) {
- Hand* hand = Application::getInstance()->getAvatar()->getHand();
- for (std::vector::iterator it = hand->getPalms().begin(); it != hand->getPalms().end(); it++) {
- it->setActive(false);
- }
- _lastMovement = usecTimestampNow();
- }
-
- if (sixenseGetNumActiveControllers() == 0) {
- _hydrasConnected = false;
- return;
- }
-
- PerformanceTimer perfTimer("sixense");
- if (!_hydrasConnected) {
- _hydrasConnected = true;
- UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra");
- }
- MyAvatar* avatar = Application::getInstance()->getAvatar();
- Hand* hand = avatar->getHand();
-
- int maxControllers = sixenseGetMaxControllers();
-
- // we only support two controllers
- sixenseControllerData controllers[2];
-
- int numActiveControllers = 0;
- for (int i = 0; i < maxControllers && numActiveControllers < 2; i++) {
- if (!sixenseIsControllerEnabled(i)) {
- continue;
- }
- sixenseControllerData* data = controllers + numActiveControllers;
- ++numActiveControllers;
- sixenseGetNewestData(i, data);
-
- // Set palm position and normal based on Hydra position/orientation
-
- // Either find a palm matching the sixense controller, or make a new one
- PalmData* palm;
- bool foundHand = false;
- for (size_t j = 0; j < hand->getNumPalms(); j++) {
- if (hand->getPalms()[j].getSixenseID() == data->controller_index) {
- palm = &(hand->getPalms()[j]);
- foundHand = true;
+ if (_isInitialized && _isEnabled) {
+ // if the controllers haven't been moved in a while, disable
+ const unsigned int MOVEMENT_DISABLE_SECONDS = 3;
+ if (usecTimestampNow() - _lastMovement > (MOVEMENT_DISABLE_SECONDS * USECS_PER_SECOND)) {
+ Hand* hand = Application::getInstance()->getAvatar()->getHand();
+ for (std::vector::iterator it = hand->getPalms().begin(); it != hand->getPalms().end(); it++) {
+ it->setActive(false);
}
- }
- if (!foundHand) {
- PalmData newPalm(hand);
- hand->getPalms().push_back(newPalm);
- palm = &(hand->getPalms()[hand->getNumPalms() - 1]);
- palm->setSixenseID(data->controller_index);
- qDebug("Found new Sixense controller, ID %i", data->controller_index);
- }
-
- palm->setActive(true);
-
- // Read controller buttons and joystick into the hand
- palm->setControllerButtons(data->buttons);
- palm->setTrigger(data->trigger);
- palm->setJoystick(data->joystick_x, data->joystick_y);
-
-
- // Emulate the mouse so we can use scripts
- if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) {
- emulateMouse(palm, numActiveControllers - 1);
- }
-
- // NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters.
- glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]);
- position *= METERS_PER_MILLIMETER;
-
- // Transform the measured position into body frame.
- glm::vec3 neck = _neckBase;
- // Zeroing y component of the "neck" effectively raises the measured position a little bit.
- neck.y = 0.f;
- position = _orbRotation * (position - neck);
-
- // Rotation of Palm
- glm::quat rotation(data->rot_quat[3], -data->rot_quat[0], data->rot_quat[1], -data->rot_quat[2]);
- rotation = glm::angleAxis(PI, glm::vec3(0.f, 1.f, 0.f)) * _orbRotation * rotation;
-
- // Compute current velocity from position change
- glm::vec3 rawVelocity;
- if (deltaTime > 0.f) {
- rawVelocity = (position - palm->getRawPosition()) / deltaTime;
- } else {
- rawVelocity = glm::vec3(0.0f);
- }
- palm->setRawVelocity(rawVelocity); // meters/sec
-
- // adjustment for hydra controllers fit into hands
- float sign = (i == 0) ? -1.0f : 1.0f;
- rotation *= glm::angleAxis(sign * PI/4.0f, glm::vec3(0.0f, 0.0f, 1.0f));
-
- if (_lowVelocityFilter) {
- // Use a velocity sensitive filter to damp small motions and preserve large ones with
- // no latency.
- float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f);
- position = palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter);
- rotation = safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter);
- palm->setRawPosition(position);
- palm->setRawRotation(rotation);
- } else {
- palm->setRawPosition(position);
- palm->setRawRotation(rotation);
- }
-
- // use the velocity to determine whether there's any movement (if the hand isn't new)
- const float MOVEMENT_DISTANCE_THRESHOLD = 0.003f;
- _amountMoved += rawVelocity * deltaTime;
- if (glm::length(_amountMoved) > MOVEMENT_DISTANCE_THRESHOLD && foundHand) {
_lastMovement = usecTimestampNow();
- _amountMoved = glm::vec3(0.0f);
}
- // Store the one fingertip in the palm structure so we can track velocity
- const float FINGER_LENGTH = 0.3f; // meters
- const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH);
- const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR;
- glm::vec3 oldTipPosition = palm->getTipRawPosition();
- if (deltaTime > 0.f) {
- palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime);
- } else {
- palm->setTipVelocity(glm::vec3(0.f));
+#ifdef __APPLE__
+ SixenseBaseFunction sixenseGetNumActiveControllers =
+ (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetNumActiveControllers");
+#endif
+
+ if (sixenseGetNumActiveControllers() == 0) {
+ _hydrasConnected = false;
+ return;
+ }
+
+ PerformanceTimer perfTimer("sixense");
+ if (!_hydrasConnected) {
+ _hydrasConnected = true;
+ UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra");
+ }
+ MyAvatar* avatar = Application::getInstance()->getAvatar();
+ Hand* hand = avatar->getHand();
+
+#ifdef __APPLE__
+ SixenseBaseFunction sixenseGetMaxControllers =
+ (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetMaxControllers");
+#endif
+
+ int maxControllers = sixenseGetMaxControllers();
+
+ // we only support two controllers
+ sixenseControllerData controllers[2];
+
+#ifdef __APPLE__
+ SixenseTakeIntFunction sixenseIsControllerEnabled =
+ (SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseIsControllerEnabled");
+
+ SixenseTakeIntAndSixenseControllerData sixenseGetNewestData =
+ (SixenseTakeIntAndSixenseControllerData) _sixenseLibrary->resolve("sixenseGetNewestData");
+#endif
+
+ int numActiveControllers = 0;
+ for (int i = 0; i < maxControllers && numActiveControllers < 2; i++) {
+ if (!sixenseIsControllerEnabled(i)) {
+ continue;
+ }
+ sixenseControllerData* data = controllers + numActiveControllers;
+ ++numActiveControllers;
+ sixenseGetNewestData(i, data);
+
+ // Set palm position and normal based on Hydra position/orientation
+
+ // Either find a palm matching the sixense controller, or make a new one
+ PalmData* palm;
+ bool foundHand = false;
+ for (size_t j = 0; j < hand->getNumPalms(); j++) {
+ if (hand->getPalms()[j].getSixenseID() == data->controller_index) {
+ palm = &(hand->getPalms()[j]);
+ foundHand = true;
+ }
+ }
+ if (!foundHand) {
+ PalmData newPalm(hand);
+ hand->getPalms().push_back(newPalm);
+ palm = &(hand->getPalms()[hand->getNumPalms() - 1]);
+ palm->setSixenseID(data->controller_index);
+ qDebug("Found new Sixense controller, ID %i", data->controller_index);
+ }
+
+ palm->setActive(true);
+
+ // Read controller buttons and joystick into the hand
+ palm->setControllerButtons(data->buttons);
+ palm->setTrigger(data->trigger);
+ palm->setJoystick(data->joystick_x, data->joystick_y);
+
+
+ // Emulate the mouse so we can use scripts
+ if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) {
+ emulateMouse(palm, numActiveControllers - 1);
+ }
+
+ // NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters.
+ glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]);
+ position *= METERS_PER_MILLIMETER;
+
+ // Transform the measured position into body frame.
+ glm::vec3 neck = _neckBase;
+ // Zeroing y component of the "neck" effectively raises the measured position a little bit.
+ neck.y = 0.f;
+ position = _orbRotation * (position - neck);
+
+ // Rotation of Palm
+ glm::quat rotation(data->rot_quat[3], -data->rot_quat[0], data->rot_quat[1], -data->rot_quat[2]);
+ rotation = glm::angleAxis(PI, glm::vec3(0.f, 1.f, 0.f)) * _orbRotation * rotation;
+
+ // Compute current velocity from position change
+ glm::vec3 rawVelocity;
+ if (deltaTime > 0.f) {
+ rawVelocity = (position - palm->getRawPosition()) / deltaTime;
+ } else {
+ rawVelocity = glm::vec3(0.0f);
+ }
+ palm->setRawVelocity(rawVelocity); // meters/sec
+
+ // adjustment for hydra controllers fit into hands
+ float sign = (i == 0) ? -1.0f : 1.0f;
+ rotation *= glm::angleAxis(sign * PI/4.0f, glm::vec3(0.0f, 0.0f, 1.0f));
+
+ if (_lowVelocityFilter) {
+ // Use a velocity sensitive filter to damp small motions and preserve large ones with
+ // no latency.
+ float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f);
+ position = palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter);
+ rotation = safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter);
+ palm->setRawPosition(position);
+ palm->setRawRotation(rotation);
+ } else {
+ palm->setRawPosition(position);
+ palm->setRawRotation(rotation);
+ }
+
+ // use the velocity to determine whether there's any movement (if the hand isn't new)
+ const float MOVEMENT_DISTANCE_THRESHOLD = 0.003f;
+ _amountMoved += rawVelocity * deltaTime;
+ if (glm::length(_amountMoved) > MOVEMENT_DISTANCE_THRESHOLD && foundHand) {
+ _lastMovement = usecTimestampNow();
+ _amountMoved = glm::vec3(0.0f);
+ }
+
+ // Store the one fingertip in the palm structure so we can track velocity
+ const float FINGER_LENGTH = 0.3f; // meters
+ const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH);
+ const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR;
+ glm::vec3 oldTipPosition = palm->getTipRawPosition();
+ if (deltaTime > 0.f) {
+ palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime);
+ } else {
+ palm->setTipVelocity(glm::vec3(0.f));
+ }
+ palm->setTipPosition(newTipPosition);
+ }
+
+ if (numActiveControllers == 2) {
+ updateCalibration(controllers);
}
- palm->setTipPosition(newTipPosition);
- }
- if (numActiveControllers == 2) {
- updateCalibration(controllers);
}
#endif // HAVE_SIXENSE
}
diff --git a/interface/src/devices/SixenseManager.h b/interface/src/devices/SixenseManager.h
index 664c102f76..1954ac91bc 100644
--- a/interface/src/devices/SixenseManager.h
+++ b/interface/src/devices/SixenseManager.h
@@ -18,6 +18,11 @@
#include
#include
#include "sixense.h"
+
+#ifdef __APPLE__
+ #include
+#endif
+
#endif
const unsigned int BUTTON_0 = 1U << 0; // the skinny button between 1 and 2
@@ -38,9 +43,12 @@ const bool DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS = false;
class SixenseManager : public QObject {
Q_OBJECT
public:
+ static SixenseManager& getInstance();
- SixenseManager();
- ~SixenseManager();
+ void initialize();
+ bool isInitialized() const { return _isInitialized; }
+
+ void setIsEnabled(bool isEnabled) { _isEnabled = isEnabled; }
void update(float deltaTime);
float getCursorPixelRangeMult() const;
@@ -51,6 +59,9 @@ public slots:
void setLowVelocityFilter(bool lowVelocityFilter) { _lowVelocityFilter = lowVelocityFilter; };
private:
+ SixenseManager();
+ ~SixenseManager();
+
#ifdef HAVE_SIXENSE
void updateCalibration(const sixenseControllerData* controllers);
void emulateMouse(PalmData* palm, int index);
@@ -72,7 +83,13 @@ private:
glm::vec3 _reachForward;
float _lastDistance;
+#ifdef __APPLE__
+ QLibrary* _sixenseLibrary;
#endif
+
+#endif
+ bool _isInitialized;
+ bool _isEnabled;
bool _hydrasConnected;
quint64 _lastMovement;
glm::vec3 _amountMoved;
diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp
index 858d8c9239..55eefb0bde 100644
--- a/interface/src/renderer/Model.cpp
+++ b/interface/src/renderer/Model.cpp
@@ -1335,11 +1335,7 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
} else {
glm::vec4 diffuse = glm::vec4(part.diffuseColor, part.opacity);
if (!(translucent && alphaThreshold == 0.0f)) {
- float emissive = (part.emissiveColor.r + part.emissiveColor.g + part.emissiveColor.b) / 3.0f;
- diffuse.a = qMax(Application::getInstance()->getGlowEffect()->getIntensity(), emissive);
- glAlphaFunc(GL_EQUAL, diffuse.a);
- diffuse = glm::vec4(qMax(diffuse.r, part.emissiveColor.r), qMax(diffuse.g, part.emissiveColor.g),
- qMax(diffuse.b, part.emissiveColor.b), diffuse.a);
+ glAlphaFunc(GL_EQUAL, diffuse.a = Application::getInstance()->getGlowEffect()->getIntensity());
}
glm::vec4 specular = glm::vec4(part.specularColor, 1.0f);
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp
index 913655195d..ed6f0cf600 100644
--- a/interface/src/scripting/WindowScriptingInterface.cpp
+++ b/interface/src/scripting/WindowScriptingInterface.cpp
@@ -265,7 +265,11 @@ QScriptValue WindowScriptingInterface::doPeekNonBlockingFormResult(QScriptValue
_form.setProperty(i, item);
} else if (item.property("options").isArray()) {
c += 1;
- item.setProperty("value", _combos.at(c)->currentText());
+ item.setProperty("value",
+ _combos.at(c)->currentIndex() < item.property("options").property("length").toInt32() ?
+ item.property("options").property(_combos.at(c)->currentIndex()) :
+ array.engine()->undefinedValue()
+ );
_form.setProperty(i, item);
} else {
e += 1;
@@ -318,7 +322,11 @@ QScriptValue WindowScriptingInterface::doGetNonBlockingFormResult(QScriptValue a
_form.setProperty(i, item);
} else if (item.property("options").isArray()) {
c += 1;
- item.setProperty("value", _combos.at(c)->currentText());
+ item.setProperty("value",
+ _combos.at(c)->currentIndex() < item.property("options").property("length").toInt32() ?
+ item.property("options").property(_combos.at(c)->currentIndex()) :
+ array.engine()->undefinedValue()
+ );
_form.setProperty(i, item);
} else {
e += 1;
@@ -349,6 +357,7 @@ QScriptValue WindowScriptingInterface::doGetNonBlockingFormResult(QScriptValue a
_form = QScriptValue();
_edits.clear();
_directories.clear();
+ _combos.clear();
array = _form;
return (_formResult == QDialog::Accepted);
@@ -391,8 +400,12 @@ QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptVal
form.setProperty(i, item);
} else if (item.property("options").isArray()) {
c += 1;
- item.setProperty("value", _combos.at(c)->currentText());
- _form.setProperty(i, item);
+ item.setProperty("value",
+ _combos.at(c)->currentIndex() < item.property("options").property("length").toInt32() ?
+ item.property("options").property(_combos.at(c)->currentIndex()) :
+ form.engine()->undefinedValue()
+ );
+ form.setProperty(i, item);
} else {
e += 1;
bool ok = true;
@@ -418,6 +431,7 @@ QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptVal
}
delete editDialog;
+ _combos.clear();
_edits.clear();
_directories.clear();
return (result == QDialog::Accepted);
@@ -498,9 +512,9 @@ QDialog* WindowScriptingInterface::createForm(const QString& title, QScriptValue
} else if (item.property("options").isArray()) {
QComboBox* combo = new QComboBox();
combo->setMinimumWidth(200);
- QStringList options = item.property("options").toVariant().toStringList();
- for (QStringList::const_iterator it = options.begin(); it != options.end(); it += 1) {
- combo->addItem(*it);
+ qint32 options_count = item.property("options").property("length").toInt32();
+ for (qint32 i = 0; i < options_count; i++) {
+ combo->addItem(item.property("options").property(i).toString());
}
_combos.push_back(combo);
formLayout->addRow(new QLabel(item.property("label").toString()), combo);
diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp
index df7c2d2289..d8d2213d8f 100644
--- a/interface/src/ui/ApplicationOverlay.cpp
+++ b/interface/src/ui/ApplicationOverlay.cpp
@@ -671,7 +671,7 @@ void ApplicationOverlay::renderControllerPointers() {
float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2));
// Get the pixel range over which the xAngle and yAngle are scaled
- float cursorRange = glWidget->width() * application->getSixenseManager()->getCursorPixelRangeMult();
+ float cursorRange = glWidget->width() * SixenseManager::getInstance().getCursorPixelRangeMult();
mouseX = (glWidget->width() / 2.0f + cursorRange * xAngle);
mouseY = (glWidget->height() / 2.0f + cursorRange * yAngle);
diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp
index 599ff28f35..916e067ae8 100644
--- a/interface/src/ui/LoginDialog.cpp
+++ b/interface/src/ui/LoginDialog.cpp
@@ -21,7 +21,7 @@
#include "LoginDialog.h"
-const QString FORGOT_PASSWORD_URL = "https://data.highfidelity.io/password/new";
+const QString FORGOT_PASSWORD_URL = "https://data.highfidelity.io/users/password/new";
LoginDialog::LoginDialog(QWidget* parent) :
FramelessDialog(parent, 0, FramelessDialog::POSITION_TOP),
diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp
index d5e1c59d7a..31c135fd58 100644
--- a/libraries/audio/src/AudioInjector.cpp
+++ b/libraries/audio/src/AudioInjector.cpp
@@ -96,6 +96,7 @@ void AudioInjector::injectAudio() {
packetStream << radius;
// pack 255 for attenuation byte
+ int volumeOptionOffset = injectAudioPacket.size();
quint8 volume = MAX_INJECTOR_VOLUME * _options.getVolume();
packetStream << volume;
@@ -118,6 +119,8 @@ void AudioInjector::injectAudio() {
memcpy(injectAudioPacket.data() + orientationOptionOffset,
&_options.getOrientation(),
sizeof(_options.getOrientation()));
+ volume = MAX_INJECTOR_VOLUME * _options.getVolume();
+ memcpy(injectAudioPacket.data() + volumeOptionOffset, &volume, sizeof(volume));
// resize the QByteArray to the right size
injectAudioPacket.resize(numPreAudioDataBytes + bytesToCopy);
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index 126da654ae..ef7083e3bf 100644
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -652,13 +652,25 @@ void AvatarData::startPlaying() {
_player->startPlaying();
}
-void AvatarData::setPlayerFrame(int frame) {
+void AvatarData::setPlayerVolume(float volume) {
+ if (_player) {
+ _player->setVolume(volume);
+ }
+}
+
+void AvatarData::setPlayerAudioOffset(int audioOffset) {
+ if (_player) {
+ _player->setAudioOffset(audioOffset);
+ }
+}
+
+void AvatarData::setPlayerFrame(unsigned int frame) {
if (_player) {
_player->setCurrentFrame(frame);
}
}
-void AvatarData::setPlayerTime(qint64 time) {
+void AvatarData::setPlayerTime(unsigned int time) {
if (_player) {
_player->setCurrentTime(time);
}
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index 9b28fdc258..1fd4052974 100755
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -100,8 +100,6 @@ enum KeyState {
DELETE_KEY_DOWN
};
-const glm::vec3 vec3Zero(0.0f);
-
class QDataStream;
class AttachmentData;
@@ -304,8 +302,10 @@ public slots:
void loadRecording(QString filename);
void startPlaying();
- void setPlayerFrame(int frame);
- void setPlayerTime(qint64 time);
+ void setPlayerVolume(float volume);
+ void setPlayerAudioOffset(int audioOffset);
+ void setPlayerFrame(unsigned int frame);
+ void setPlayerTime(unsigned int time);
void setPlayFromCurrentLocation(bool playFromCurrentLocation);
void setPlayerLoop(bool loop);
void setPlayerUseDisplayName(bool useDisplayName);
diff --git a/libraries/avatars/src/Player.cpp b/libraries/avatars/src/Player.cpp
index 9732ccd780..35a822f11b 100644
--- a/libraries/avatars/src/Player.cpp
+++ b/libraries/avatars/src/Player.cpp
@@ -17,11 +17,16 @@
#include "AvatarData.h"
#include "Player.h"
+static const int INVALID_FRAME = -1;
+
Player::Player(AvatarData* avatar) :
- _recording(new Recording()),
- _pausedFrame(-1),
- _timerOffset(0),
_avatar(avatar),
+ _recording(new Recording()),
+ _currentFrame(INVALID_FRAME),
+ _frameInterpolationFactor(0.0f),
+ _pausedFrame(INVALID_FRAME),
+ _timerOffset(0),
+ _audioOffset(0),
_audioThread(NULL),
_playFromCurrentPosition(true),
_loop(false),
@@ -31,8 +36,6 @@ Player::Player(AvatarData* avatar) :
_useSkeletonURL(true)
{
_timer.invalidate();
- _options.setLoop(false);
- _options.setVolume(1.0f);
}
bool Player::isPlaying() const {
@@ -40,7 +43,7 @@ bool Player::isPlaying() const {
}
bool Player::isPaused() const {
- return (_pausedFrame != -1);
+ return (_pausedFrame != INVALID_FRAME);
}
qint64 Player::elapsed() const {
@@ -120,7 +123,7 @@ void Player::startPlaying() {
_timer.start();
setCurrentFrame(_pausedFrame);
- _pausedFrame = -1;
+ _pausedFrame = INVALID_FRAME;
}
}
@@ -128,7 +131,7 @@ void Player::stopPlaying() {
if (!isPlaying()) {
return;
}
- _pausedFrame = -1;
+ _pausedFrame = INVALID_FRAME;
_timer.invalidate();
cleanupAudioThread();
_avatar->clearJointsData();
@@ -199,12 +202,12 @@ void Player::loadFromFile(const QString& file) {
}
readRecordingFromFile(_recording, file);
- _pausedFrame = -1;
+ _pausedFrame = INVALID_FRAME;
}
void Player::loadRecording(RecordingPointer recording) {
_recording = recording;
- _pausedFrame = -1;
+ _pausedFrame = INVALID_FRAME;
}
void Player::play() {
@@ -223,22 +226,68 @@ void Player::play() {
context = &_currentContext;
}
const RecordingFrame& currentFrame = _recording->getFrame(_currentFrame);
+ const RecordingFrame& nextFrame = _recording->getFrame(_currentFrame + 1);
- _avatar->setPosition(context->position + context->orientation * currentFrame.getTranslation());
- _avatar->setOrientation(context->orientation * currentFrame.getRotation());
- _avatar->setTargetScale(context->scale * currentFrame.getScale());
- _avatar->setJointRotations(currentFrame.getJointRotations());
+ glm::vec3 translation = glm::mix(currentFrame.getTranslation(),
+ nextFrame.getTranslation(),
+ _frameInterpolationFactor);
+ _avatar->setPosition(context->position + context->orientation * translation);
+
+ glm::quat rotation = safeMix(currentFrame.getRotation(),
+ nextFrame.getRotation(),
+ _frameInterpolationFactor);
+ _avatar->setOrientation(context->orientation * rotation);
+
+ float scale = glm::mix(currentFrame.getScale(),
+ nextFrame.getScale(),
+ _frameInterpolationFactor);
+ _avatar->setTargetScale(context->scale * scale);
+
+
+ QVector jointRotations(currentFrame.getJointRotations().size());
+ for (int i = 0; i < currentFrame.getJointRotations().size(); ++i) {
+ jointRotations[i] = safeMix(currentFrame.getJointRotations()[i],
+ nextFrame.getJointRotations()[i],
+ _frameInterpolationFactor);
+ }
+ _avatar->setJointRotations(jointRotations);
HeadData* head = const_cast(_avatar->getHeadData());
if (head) {
- head->setBlendshapeCoefficients(currentFrame.getBlendshapeCoefficients());
- head->setLeanSideways(currentFrame.getLeanSideways());
- head->setLeanForward(currentFrame.getLeanForward());
- glm::vec3 eulers = glm::degrees(safeEulerAngles(currentFrame.getHeadRotation()));
+ // Make sure fake faceshift connection doesn't get turned off
+ _avatar->setForceFaceshiftConnected(true);
+
+ QVector blendCoef(currentFrame.getBlendshapeCoefficients().size());
+ for (int i = 0; i < currentFrame.getBlendshapeCoefficients().size(); ++i) {
+ blendCoef[i] = glm::mix(currentFrame.getBlendshapeCoefficients()[i],
+ nextFrame.getBlendshapeCoefficients()[i],
+ _frameInterpolationFactor);
+ }
+ head->setBlendshapeCoefficients(blendCoef);
+
+ float leanSideways = glm::mix(currentFrame.getLeanSideways(),
+ nextFrame.getLeanSideways(),
+ _frameInterpolationFactor);
+ head->setLeanSideways(leanSideways);
+
+ float leanForward = glm::mix(currentFrame.getLeanForward(),
+ nextFrame.getLeanForward(),
+ _frameInterpolationFactor);
+ head->setLeanForward(leanForward);
+
+ glm::quat headRotation = safeMix(currentFrame.getHeadRotation(),
+ nextFrame.getHeadRotation(),
+ _frameInterpolationFactor);
+ glm::vec3 eulers = glm::degrees(safeEulerAngles(headRotation));
head->setFinalPitch(eulers.x);
head->setFinalYaw(eulers.y);
head->setFinalRoll(eulers.z);
- head->setLookAtPosition(context->position + context->orientation * currentFrame.getLookAtPosition());
+
+
+ glm::vec3 lookAt = glm::mix(currentFrame.getLookAtPosition(),
+ nextFrame.getLookAtPosition(),
+ _frameInterpolationFactor);
+ head->setLookAtPosition(context->position + context->orientation * lookAt);
} else {
qDebug() << "WARNING: Player couldn't find head data.";
}
@@ -248,8 +297,8 @@ void Player::play() {
_injector->setOptions(_options);
}
-void Player::setCurrentFrame(int currentFrame) {
- if (_recording && (currentFrame < 0 || currentFrame >= _recording->getFrameNumber())) {
+void Player::setCurrentFrame(unsigned int currentFrame) {
+ if (_recording && currentFrame >= _recording->getFrameNumber()) {
stopPlaying();
return;
}
@@ -261,12 +310,12 @@ void Player::setCurrentFrame(int currentFrame) {
_timer.start();
setAudionInjectorPosition();
} else {
- _pausedFrame = currentFrame;
+ _pausedFrame = _currentFrame;
}
}
-void Player::setCurrentTime(qint64 currentTime) {
- if (currentTime < 0 || currentTime >= _recording->getLength()) {
+void Player::setCurrentTime(unsigned int currentTime) {
+ if (currentTime >= _recording->getLength()) {
stopPlaying();
return;
}
@@ -310,6 +359,18 @@ void Player::setCurrentTime(qint64 currentTime) {
}
}
+void Player::setVolume(float volume) {
+ _options.setVolume(volume);
+ if (_injector) {
+ _injector->setOptions(_options);
+ }
+ qDebug() << "New volume: " << volume;
+}
+
+void Player::setAudioOffset(int audioOffset) {
+ _audioOffset = audioOffset;
+}
+
void Player::setAudionInjectorPosition() {
int MSEC_PER_SEC = 1000;
int SAMPLE_SIZE = 2; // 16 bits
@@ -325,17 +386,38 @@ void Player::setPlayFromCurrentLocation(bool playFromCurrentLocation) {
bool Player::computeCurrentFrame() {
if (!isPlaying()) {
- _currentFrame = -1;
+ _currentFrame = INVALID_FRAME;
return false;
}
if (_currentFrame < 0) {
_currentFrame = 0;
}
- while (_currentFrame < _recording->getFrameNumber() - 1 &&
- _recording->getFrameTimestamp(_currentFrame) < elapsed()) {
- ++_currentFrame;
+ quint64 elapsed = glm::clamp(Player::elapsed() - _audioOffset, (qint64)0, (qint64)_recording->getLength());
+ while(_currentFrame >= 0 &&
+ _recording->getFrameTimestamp(_currentFrame) > elapsed) {
+ --_currentFrame;
}
+ while (_currentFrame < _recording->getFrameNumber() &&
+ _recording->getFrameTimestamp(_currentFrame) < elapsed) {
+ ++_currentFrame;
+ }
+ --_currentFrame;
+
+ if (_currentFrame == _recording->getFrameNumber() - 1) {
+ --_currentFrame;
+ _frameInterpolationFactor = 1.0f;
+ } else {
+ qint64 currentTimestamps = _recording->getFrameTimestamp(_currentFrame);
+ qint64 nextTimestamps = _recording->getFrameTimestamp(_currentFrame + 1);
+ _frameInterpolationFactor = (float)(elapsed - currentTimestamps) /
+ (float)(nextTimestamps - currentTimestamps);
+ }
+
+ if (_frameInterpolationFactor < 0.0f || _frameInterpolationFactor > 1.0f) {
+ _frameInterpolationFactor = 0.0f;
+ qDebug() << "Invalid frame interpolation value: overriding";
+ }
return true;
}
diff --git a/libraries/avatars/src/Player.h b/libraries/avatars/src/Player.h
index 51e120917c..043fcc4a25 100644
--- a/libraries/avatars/src/Player.h
+++ b/libraries/avatars/src/Player.h
@@ -44,8 +44,11 @@ public slots:
void loadRecording(RecordingPointer recording);
void play();
- void setCurrentFrame(int currentFrame);
- void setCurrentTime(qint64 currentTime);
+ void setCurrentFrame(unsigned int currentFrame);
+ void setCurrentTime(unsigned int currentTime);
+
+ void setVolume(float volume);
+ void setAudioOffset(int audioOffset);
void setPlayFromCurrentLocation(bool playFromCurrentPosition);
void setLoop(bool loop) { _loop = loop; }
@@ -61,18 +64,20 @@ private:
void setAudionInjectorPosition();
bool computeCurrentFrame();
- QElapsedTimer _timer;
+ AvatarData* _avatar;
RecordingPointer _recording;
int _currentFrame;
+ float _frameInterpolationFactor;
int _pausedFrame;
- qint64 _timerOffset;
+
+ QElapsedTimer _timer;
+ int _timerOffset;
+ int _audioOffset;
+ QThread* _audioThread;
QSharedPointer _injector;
AudioInjectorOptions _options;
- AvatarData* _avatar;
- QThread* _audioThread;
-
RecordingContext _currentContext;
bool _playFromCurrentPosition;
bool _loop;
diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp
index 88e4bad7b2..f79b66b539 100644
--- a/libraries/networking/src/AccountManager.cpp
+++ b/libraries/networking/src/AccountManager.cpp
@@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include
+
#include
#include
#include
@@ -25,9 +27,14 @@
const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false;
-AccountManager& AccountManager::getInstance() {
- static AccountManager sharedInstance;
- return sharedInstance;
+AccountManager& AccountManager::getInstance(bool forceReset) {
+ static std::auto_ptr sharedInstance(new AccountManager());
+
+ if (forceReset) {
+ sharedInstance.reset(new AccountManager());
+ }
+
+ return *sharedInstance;
}
Q_DECLARE_METATYPE(OAuthAccessToken)
diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h
index edccab0b75..8d1a127408 100644
--- a/libraries/networking/src/AccountManager.h
+++ b/libraries/networking/src/AccountManager.h
@@ -42,7 +42,7 @@ const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization";
class AccountManager : public QObject {
Q_OBJECT
public:
- static AccountManager& getInstance();
+ static AccountManager& getInstance(bool forceReset = false);
void authenticatedRequest(const QString& path,
QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp
index d42cab6210..2c8e968375 100644
--- a/libraries/networking/src/LimitedNodeList.cpp
+++ b/libraries/networking/src/LimitedNodeList.cpp
@@ -35,29 +35,32 @@ const char SOLO_NODE_TYPES[2] = {
const QUrl DEFAULT_NODE_AUTH_URL = QUrl("https://data.highfidelity.io");
-LimitedNodeList* LimitedNodeList::_sharedInstance = NULL;
+std::auto_ptr LimitedNodeList::_sharedInstance;
LimitedNodeList* LimitedNodeList::createInstance(unsigned short socketListenPort, unsigned short dtlsPort) {
- if (!_sharedInstance) {
- NodeType::init();
+ NodeType::init();
+
+ if (_sharedInstance.get()) {
+ qDebug() << "LimitedNodeList called with existing instance." <<
+ "Releasing auto_ptr, deleting existing instance and creating a new one.";
- _sharedInstance = new LimitedNodeList(socketListenPort, dtlsPort);
-
- // register the SharedNodePointer meta-type for signals/slots
- qRegisterMetaType();
- } else {
- qDebug("LimitedNodeList createInstance called with existing instance.");
+ delete _sharedInstance.release();
}
+
+ _sharedInstance = std::auto_ptr(new LimitedNodeList(socketListenPort, dtlsPort));
+
+ // register the SharedNodePointer meta-type for signals/slots
+ qRegisterMetaType();
- return _sharedInstance;
+ return _sharedInstance.get();
}
LimitedNodeList* LimitedNodeList::getInstance() {
- if (!_sharedInstance) {
+ if (!_sharedInstance.get()) {
qDebug("LimitedNodeList getInstance called before call to createInstance. Returning NULL pointer.");
}
- return _sharedInstance;
+ return _sharedInstance.get();
}
diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h
index 337d66616e..b34845719c 100644
--- a/libraries/networking/src/LimitedNodeList.h
+++ b/libraries/networking/src/LimitedNodeList.h
@@ -14,6 +14,7 @@
#include
#include
+#include
#ifndef _WIN32
#include // not on windows, not needed for mac or windows
@@ -118,7 +119,7 @@ signals:
void nodeKilled(SharedNodePointer);
void publicSockAddrChanged(const HifiSockAddr& publicSockAddr);
protected:
- static LimitedNodeList* _sharedInstance;
+ static std::auto_ptr _sharedInstance;
LimitedNodeList(unsigned short socketListenPort, unsigned short dtlsListenPort);
LimitedNodeList(LimitedNodeList const&); // Don't implement, needed to avoid copies of singleton
diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp
index 7ae9f9ba00..3a1ed79f77 100644
--- a/libraries/networking/src/NodeList.cpp
+++ b/libraries/networking/src/NodeList.cpp
@@ -24,30 +24,31 @@
#include "SharedUtil.h"
#include "UUID.h"
-NodeList* NodeList::_sharedInstance = NULL;
-
NodeList* NodeList::createInstance(char ownerType, unsigned short socketListenPort, unsigned short dtlsPort) {
- if (!_sharedInstance) {
- NodeType::init();
+
+ NodeType::init();
+
+ if (_sharedInstance.get()) {
+ qDebug() << "NodeList called with existing instance." <<
+ "Releasing auto_ptr, deleting existing instance and creating a new one.";
- _sharedInstance = new NodeList(ownerType, socketListenPort, dtlsPort);
- LimitedNodeList::_sharedInstance = _sharedInstance;
-
- // register the SharedNodePointer meta-type for signals/slots
- qRegisterMetaType();
- } else {
- qDebug("NodeList createInstance called with existing instance.");
+ delete _sharedInstance.release();
}
-
- return _sharedInstance;
+
+ _sharedInstance = std::auto_ptr(new NodeList(ownerType, socketListenPort, dtlsPort));
+
+ // register the SharedNodePointer meta-type for signals/slots
+ qRegisterMetaType();
+
+ return static_cast(_sharedInstance.get());
}
NodeList* NodeList::getInstance() {
- if (!_sharedInstance) {
+ if (!_sharedInstance.get()) {
qDebug("NodeList getInstance called before call to createInstance. Returning NULL pointer.");
}
- return _sharedInstance;
+ return static_cast(_sharedInstance.get());
}
NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned short dtlsListenPort) :
diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h
index d17e56fd48..8293d0a05a 100644
--- a/libraries/networking/src/NodeList.h
+++ b/libraries/networking/src/NodeList.h
@@ -84,8 +84,6 @@ public slots:
signals:
void limitOfSilentDomainCheckInsReached();
private:
- static NodeList* _sharedInstance;
-
NodeList(char ownerType, unsigned short socketListenPort, unsigned short dtlsListenPort);
NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton
void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton
diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp
index d3626e0249..648f15648a 100644
--- a/libraries/shared/src/HifiConfigVariantMap.cpp
+++ b/libraries/shared/src/HifiConfigVariantMap.cpp
@@ -87,43 +87,48 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
return mergedMap;
}
-QVariantMap HifiConfigVariantMap::mergeMasterConfigWithUserConfig(const QStringList& argumentList) {
+HifiConfigVariantMap::HifiConfigVariantMap() :
+ _userConfigFilename(),
+ _masterConfig(),
+ _userConfig(),
+ _mergedConfig()
+{
+
+}
+
+void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentList) {
// check if there is a master config file
const QString MASTER_CONFIG_FILE_OPTION = "--master-config";
- QVariantMap configVariantMap;
-
int masterConfigIndex = argumentList.indexOf(MASTER_CONFIG_FILE_OPTION);
if (masterConfigIndex != -1) {
QString masterConfigFilepath = argumentList[masterConfigIndex + 1];
- mergeMapWithJSONFile(configVariantMap, masterConfigFilepath);
+ loadMapFromJSONFile(_masterConfig, masterConfigFilepath);
}
- // merge the existing configVariantMap with the user config file
- mergeMapWithJSONFile(configVariantMap, userConfigFilepath(argumentList));
-
- return configVariantMap;
-}
-
-QString HifiConfigVariantMap::userConfigFilepath(const QStringList& argumentList) {
- // we've loaded up the master config file, now fill in anything it didn't have with the user config file
+ // load the user config
const QString USER_CONFIG_FILE_OPTION = "--user-config";
int userConfigIndex = argumentList.indexOf(USER_CONFIG_FILE_OPTION);
- QString userConfigFilepath;
if (userConfigIndex != -1) {
- userConfigFilepath = argumentList[userConfigIndex + 1];
+ _userConfigFilename = argumentList[userConfigIndex + 1];
} else {
- userConfigFilepath = QString("%1/%2/%3/config.json").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation),
- QCoreApplication::organizationName(),
- QCoreApplication::applicationName());
+ _userConfigFilename = QString("%1/%2/%3/config.json").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation),
+ QCoreApplication::organizationName(),
+ QCoreApplication::applicationName());
}
- return userConfigFilepath;
+ loadMapFromJSONFile(_userConfig, _userConfigFilename);
+
+ // the merged config is initially matched to the master config
+ _mergedConfig = _masterConfig;
+
+ // then we merge in anything missing from the user config
+ addMissingValuesToExistingMap(_mergedConfig, _userConfig);
}
-void HifiConfigVariantMap::mergeMapWithJSONFile(QVariantMap& existingMap, const QString& filename) {
+void HifiConfigVariantMap::loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename) {
QFile configFile(filename);
if (configFile.exists()) {
@@ -131,12 +136,7 @@ void HifiConfigVariantMap::mergeMapWithJSONFile(QVariantMap& existingMap, const
configFile.open(QIODevice::ReadOnly);
QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll());
-
- if (existingMap.isEmpty()) {
- existingMap = configDocument.toVariant().toMap();
- } else {
- addMissingValuesToExistingMap(existingMap, configDocument.toVariant().toMap());
- }
+ existingMap = configDocument.toVariant().toMap();
} else {
qDebug() << "Could not find JSON config file at" << filename;
diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h
index b1b6b55aa2..6bdeb15589 100644
--- a/libraries/shared/src/HifiConfigVariantMap.h
+++ b/libraries/shared/src/HifiConfigVariantMap.h
@@ -17,11 +17,24 @@
class HifiConfigVariantMap {
public:
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
- static QVariantMap mergeMasterConfigWithUserConfig(const QStringList& argumentList);
- static QString userConfigFilepath(const QStringList& argumentList);
+
+ HifiConfigVariantMap();
+ void loadMasterAndUserConfig(const QStringList& argumentList);
+
+ const QVariantMap& getMasterConfig() const { return _masterConfig; }
+ QVariantMap& getUserConfig() { return _userConfig; }
+ QVariantMap& getMergedConfig() { return _mergedConfig; }
+
+ const QString& getUserConfigFilename() const { return _userConfigFilename; }
private:
- static void mergeMapWithJSONFile(QVariantMap& existingMap, const QString& filename);
- static void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
+ QString _userConfigFilename;
+
+ QVariantMap _masterConfig;
+ QVariantMap _userConfig;
+ QVariantMap _mergedConfig;
+
+ void loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename);
+ void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
};
const QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath);