3
0
Fork 0
mirror of https://github.com/lubosz/overte.git synced 2025-04-12 10:02:25 +02:00

Merge branch 'master' of https://github.com/highfidelity/hifi into skin

This commit is contained in:
samcake 2016-06-24 09:37:52 -07:00
commit ec68eba3cb
156 changed files with 5177 additions and 2945 deletions
domain-server/src
interface
libraries

View file

@ -63,12 +63,16 @@ const QString DomainMetadata::Descriptors::Hours::CLOSE = "close";
DomainMetadata::DomainMetadata(QObject* domainServer) : QObject(domainServer) {
// set up the structure necessary for casting during parsing (see parseHours, esp.)
_metadata[USERS] = QVariantMap {};
_metadata[DESCRIPTORS] = QVariantMap {
{ Descriptors::HOURS, QVariantMap {
{ Descriptors::Hours::WEEKDAY, QVariantList { QVariantMap{} } },
{ Descriptors::Hours::WEEKEND, QVariantList { QVariantMap{} } }
} }
};
_metadata[DESCRIPTORS] = QVariantMap { {
Descriptors::HOURS, QVariantMap {
{ Descriptors::Hours::WEEKDAY, QVariantList {
QVariantList{ QVariant{}, QVariant{} } }
},
{ Descriptors::Hours::WEEKEND, QVariantList {
QVariantList{ QVariant{}, QVariant{} } }
}
}
} };
assert(dynamic_cast<DomainServer*>(domainServer));
DomainServer* server = static_cast<DomainServer*>(domainServer);
@ -96,33 +100,37 @@ QJsonObject DomainMetadata::get(const QString& group) {
return QJsonObject::fromVariantMap(_metadata[group].toMap());
}
// merge delta into target
// target should be of the form [ OpenTime, CloseTime ],
// delta should be of the form [ { open: Time, close: Time } ]
void parseHours(QVariant delta, QVariant& target) {
using Hours = DomainMetadata::Descriptors::Hours;
// hours should be of the form [ { open: Time, close: Time } ]
assert(target.canConvert<QVariantList>());
auto& targetList = *static_cast<QVariantList*>(target.data());
// if/when multiple ranges are allowed, this list will need to be iterated
assert(targetList[0].canConvert<QVariantMap>());
auto& targetMap = *static_cast<QVariantMap*>(targetList[0].data());
assert(targetList[0].canConvert<QVariantList>());
auto& hours = *static_cast<QVariantList*>(targetList[0].data());
auto deltaMap = delta.toList()[0].toMap();
if (deltaMap.isEmpty()) {
auto deltaHours = delta.toList()[0].toMap();
if (deltaHours.isEmpty()) {
return;
}
// merge delta into base
auto open = deltaMap.find(Hours::OPEN);
if (open != deltaMap.end()) {
targetMap[Hours::OPEN] = open.value();
static const int OPEN_INDEX = 0;
static const int CLOSE_INDEX = 1;
auto open = deltaHours.find(Hours::OPEN);
if (open != deltaHours.end()) {
hours[OPEN_INDEX] = open.value();
}
assert(targetMap[Hours::OPEN].canConvert<QString>());
auto close = deltaMap.find(Hours::CLOSE);
if (close != deltaMap.end()) {
targetMap[Hours::CLOSE] = close.value();
assert(hours[OPEN_INDEX].canConvert<QString>());
auto close = deltaHours.find(Hours::CLOSE);
if (close != deltaHours.end()) {
hours[CLOSE_INDEX] = close.value();
}
assert(targetMap[Hours::CLOSE].canConvert<QString>());
assert(hours[CLOSE_INDEX].canConvert<QString>());
}
void DomainMetadata::descriptorsChanged() {

View file

@ -1,18 +1,26 @@
{
"name": "Vive to Standard",
"channels": [
{ "from": "Vive.LY", "when": "Vive.LSOuter", "filters": ["invert"], "to": "Standard.LY" },
{ "from": "Vive.LX", "when": "Vive.LSOuter", "to": "Standard.LX" },
{ "from": "Vive.LT", "to": "Standard.LT" },
{ "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" },
{ "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" },
{
"from": "Vive.LT", "to": "Standard.LT",
"filters": [
{ "type": "deadZone", "min": 0.05 }
]
},
{ "from": "Vive.LeftGrip", "to": "Standard.LeftGrip" },
{ "from": "Vive.LS", "to": "Standard.LS" },
{ "from": "Vive.LSTouch", "to": "Standard.LSTouch" },
{ "from": "Vive.RY", "when": "Vive.RSOuter", "filters": ["invert"], "to": "Standard.RY" },
{ "from": "Vive.RX", "when": "Vive.RSOuter", "to": "Standard.RX" },
{ "from": "Vive.RT", "to": "Standard.RT" },
{ "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" },
{ "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" },
{
"from": "Vive.RT", "to": "Standard.RT",
"filters": [
{ "type": "deadZone", "min": 0.05 }
]
},
{ "from": "Vive.RightGrip", "to": "Standard.RightGrip" },
{ "from": "Vive.RS", "to": "Standard.RS" },
{ "from": "Vive.RSTouch", "to": "Standard.RSTouch" },

View file

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.49;}
.st1{fill:#1E1E1E;}
.st2{opacity:0.5;}
.st3{fill:#EAEAEA;}
.st4{fill:#FFFFFF;}
</style>
<g class="st0">
<path class="st1" d="M50,146c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146z"/>
</g>
<g class="st2">
<g>
<path class="st3" d="M25.1,109.5c-4.2,0-8,1.8-10.8,4.8c0,0-0.5,0.7,0.2,1.2c0.7,0.6,1.4-0.1,1.4-0.1c2.4-2.4,5.7-3.9,9.2-3.9
c3.6,0,6.9,1.5,9.3,4.1c0,0,0.7,0.8,1.5,0.3c0.7-0.4,0.2-1.2,0.2-1.3C33.3,111.4,29.4,109.5,25.1,109.5z"/>
<path class="st3" d="M35.4,116.3c-0.2,0-0.4,0-0.5-0.1c-0.2-0.1-0.5-0.3-0.6-0.4l0,0c-2.5-2.6-5.7-4-9.2-4c-3.4,0-6.6,1.4-9.1,3.9
l0,0c-0.1,0.1-0.3,0.2-0.4,0.3c-0.5,0.2-0.9,0.2-1.3-0.2c-0.6-0.5-0.4-1.1-0.3-1.4c0-0.1,0.1-0.1,0.1-0.2l0,0
c2.9-3.1,6.8-4.9,10.9-4.9c4.2,0,8.2,1.8,11.2,5.1c0.1,0.1,0.3,0.5,0.2,0.9c0,0.3-0.2,0.6-0.5,0.8
C35.9,116.2,35.6,116.3,35.4,116.3z M34.7,115.4c0.1,0.1,0.6,0.6,1.1,0.2c0.1-0.1,0.2-0.2,0.2-0.3c0-0.3-0.1-0.5-0.1-0.6
c-2.9-3.2-6.7-5-10.8-5c-4,0-7.7,1.7-10.6,4.7c-0.1,0.1-0.3,0.5,0.1,0.9c0.5,0.4,1,0,1.1-0.1c2.6-2.6,5.9-4,9.4-4
C28.7,111.2,32.1,112.7,34.7,115.4L34.7,115.4z M36,114.7C36,114.7,36,114.7,36,114.7L36,114.7z M35.9,114.7
C35.9,114.7,35.9,114.7,35.9,114.7L35.9,114.7z"/>
</g>
<path class="st3" d="M27.3,116l-1.5-0.9c-0.3-0.2-0.7-0.2-1,0l-1.5,0.9c-0.3,0.2-0.5,0.5-0.5,0.9l0,1.8c0,0.4,0.2,0.7,0.5,0.9
l1.5,0.9c0.2,0.1,0.3,0.1,0.5,0.1c0.2,0,0.4,0,0.5-0.1l1.5-0.9c0.3-0.2,0.5-0.5,0.5-0.9l0-1.8C27.8,116.5,27.6,116.2,27.3,116z"/>
<path class="st3" d="M31.2,125l-1.2-1.5c-0.2-0.3-0.5-0.4-0.8-0.4l-7.9,0c0,0,0,0,0,0c-0.3,0-0.6,0.1-0.8,0.4l-1.2,1.5
c-0.3,0.4-0.3,0.9,0,1.3l1.2,1.5c0.2,0.3,0.5,0.4,0.8,0.4l7.9,0c0,0,0,0,0,0c0.3,0,0.6-0.1,0.8-0.4l1.2-1.5
C31.5,126,31.5,125.4,31.2,125z"/>
</g>
<g class="st2">
<path class="st3" d="M19.2,135.5v6.4H18v-2.7h-2.9v2.7h-1.2v-6.4h1.2v2.6H18v-2.6H19.2z"/>
<path class="st3" d="M25.4,141c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7c0.1-0.3,0.1-0.5,0.1-0.8
v-3.4h1.3v3.4c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3
c-0.5,0-1-0.1-1.4-0.3c-0.4-0.2-0.7-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.2-0.8-0.2-1.2v-3.4h1.3v3.4
c0,0.3,0,0.5,0.1,0.8c0.1,0.3,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.4,0.5,0.5S25.1,141,25.4,141z"/>
<path class="st3" d="M31.7,141.9v-6.4H34c0.5,0,1,0.1,1.4,0.3c0.4,0.2,0.7,0.4,1,0.7c0.3,0.3,0.5,0.6,0.6,1
c0.1,0.4,0.2,0.8,0.2,1.2c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.6c-0.4,0.2-0.8,0.2-1.3,0.2H31.7z
M35.9,138.7c0-0.3,0-0.6-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.3-0.6-0.4c-0.2-0.1-0.5-0.2-0.8-0.2h-1.1v4.2H34
c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.6-0.4c0.2-0.2,0.3-0.4,0.4-0.7C35.8,139.3,35.9,139,35.9,138.7z"/>
</g>
<g>
<path class="st1" d="M50,96c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V54c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96z"/>
</g>
<g>
<g>
<path class="st3" d="M25.1,59.5c-4.2,0-8,1.8-10.8,4.8c0,0-0.5,0.7,0.2,1.2c0.7,0.6,1.4-0.1,1.4-0.1c2.4-2.4,5.7-3.9,9.2-3.9
c3.6,0,6.9,1.5,9.3,4.1c0,0,0.7,0.8,1.5,0.3c0.7-0.4,0.2-1.2,0.2-1.3C33.3,61.5,29.4,59.5,25.1,59.5z"/>
<path class="st3" d="M35.4,66.3c-0.2,0-0.4,0-0.5-0.1c-0.2-0.1-0.5-0.3-0.6-0.4l0,0c-2.5-2.6-5.7-4-9.2-4c-3.4,0-6.6,1.4-9.1,3.9
l0,0c-0.1,0.1-0.3,0.2-0.4,0.3c-0.5,0.2-0.9,0.2-1.3-0.2c-0.6-0.5-0.4-1.1-0.3-1.4c0-0.1,0.1-0.1,0.1-0.2l0,0
c2.9-3.1,6.8-4.9,10.9-4.9c4.2,0,8.2,1.8,11.2,5.1c0.1,0.1,0.3,0.5,0.2,0.9c0,0.3-0.2,0.6-0.5,0.8C35.9,66.2,35.6,66.3,35.4,66.3z
M34.7,65.5c0.1,0.1,0.6,0.6,1.1,0.2c0.1-0.1,0.2-0.2,0.2-0.3c0-0.3-0.1-0.5-0.1-0.6c-2.9-3.2-6.7-5-10.8-5c-4,0-7.7,1.7-10.6,4.7
c-0.1,0.1-0.3,0.5,0.1,0.9c0.5,0.4,1,0,1.1-0.1c2.6-2.6,5.9-4,9.4-4C28.7,61.3,32.1,62.7,34.7,65.5L34.7,65.5z M36,64.7
C36,64.7,36,64.7,36,64.7L36,64.7z M35.9,64.7C35.9,64.7,35.9,64.7,35.9,64.7L35.9,64.7z"/>
</g>
<path class="st3" d="M27.3,66l-1.5-0.9c-0.3-0.2-0.7-0.2-1,0L23.2,66c-0.3,0.2-0.5,0.5-0.5,0.9l0,1.8c0,0.4,0.2,0.7,0.5,0.9
l1.5,0.9c0.2,0.1,0.3,0.1,0.5,0.1c0.2,0,0.4,0,0.5-0.1l1.5-0.9c0.3-0.2,0.5-0.5,0.5-0.9l0-1.8C27.8,66.5,27.6,66.2,27.3,66z"/>
<path class="st3" d="M31.2,75.1L30,73.5c-0.2-0.3-0.5-0.4-0.8-0.4l-7.9,0c0,0,0,0,0,0c-0.3,0-0.6,0.1-0.8,0.4l-1.2,1.5
c-0.3,0.4-0.3,0.9,0,1.3l1.2,1.5c0.2,0.3,0.5,0.4,0.8,0.4l7.9,0c0,0,0,0,0,0c0.3,0,0.6-0.1,0.8-0.4l1.2-1.5
C31.5,76,31.5,75.5,31.2,75.1z"/>
</g>
<g>
<path class="st3" d="M19.2,85.5v6.4H18v-2.7h-2.9v2.7h-1.2v-6.4h1.2v2.6H18v-2.6H19.2z"/>
<path class="st3" d="M25.4,91c0.3,0,0.6-0.1,0.8-0.2s0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.4h1.3v3.4
c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-1-0.1-1.4-0.3
c-0.4-0.2-0.7-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.2-0.8-0.2-1.2v-3.4h1.3v3.4c0,0.3,0,0.5,0.1,0.8
c0.1,0.3,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.4,0.5,0.5S25.1,91,25.4,91z"/>
<path class="st3" d="M31.7,91.9v-6.4H34c0.5,0,1,0.1,1.4,0.3c0.4,0.2,0.7,0.4,1,0.7c0.3,0.3,0.5,0.6,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2
c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.6c-0.4,0.2-0.8,0.2-1.3,0.2H31.7z M35.9,88.7
c0-0.3,0-0.6-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.3-0.6-0.4c-0.2-0.1-0.5-0.2-0.8-0.2h-1.1v4.2H34
c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.6-0.4c0.2-0.2,0.3-0.4,0.4-0.7C35.8,89.3,35.9,89,35.9,88.7z"/>
</g>
<g>
<path class="st4" d="M50,46.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46.1z"/>
</g>
<g>
<g>
<path d="M25.1,9.6c-4.2,0-8,1.8-10.8,4.8c0,0-0.5,0.7,0.2,1.2c0.7,0.6,1.4-0.1,1.4-0.1c2.4-2.4,5.7-3.9,9.2-3.9
c3.6,0,6.9,1.5,9.3,4.1c0,0,0.7,0.8,1.5,0.3c0.7-0.4,0.2-1.2,0.2-1.3C33.3,11.5,29.4,9.6,25.1,9.6z"/>
<path d="M35.4,16.3c-0.2,0-0.4,0-0.5-0.1c-0.2-0.1-0.5-0.3-0.6-0.4l0,0c-2.5-2.6-5.7-4-9.2-4c-3.4,0-6.6,1.4-9.1,3.9l0,0
c-0.1,0.1-0.3,0.2-0.4,0.3c-0.5,0.2-0.9,0.2-1.3-0.2c-0.6-0.5-0.4-1.1-0.3-1.4c0-0.1,0.1-0.1,0.1-0.2l0,0
c2.9-3.1,6.8-4.9,10.9-4.9c4.2,0,8.2,1.8,11.2,5.1c0.1,0.1,0.3,0.5,0.2,0.9c0,0.3-0.2,0.6-0.5,0.8C35.9,16.3,35.6,16.3,35.4,16.3z
M34.7,15.5c0.1,0.1,0.6,0.6,1.1,0.2c0.1-0.1,0.2-0.2,0.2-0.3c0-0.3-0.1-0.5-0.1-0.6c-2.9-3.2-6.7-5-10.8-5c-4,0-7.7,1.7-10.6,4.7
c-0.1,0.1-0.3,0.5,0.1,0.9c0.5,0.4,1,0,1.1-0.1c2.6-2.6,5.9-4,9.4-4C28.7,11.3,32.1,12.8,34.7,15.5L34.7,15.5z M36,14.8
C36,14.8,36,14.8,36,14.8L36,14.8z M35.9,14.8C35.9,14.8,35.9,14.8,35.9,14.8L35.9,14.8z"/>
</g>
<path d="M27.3,16l-1.5-0.9c-0.3-0.2-0.7-0.2-1,0L23.2,16c-0.3,0.2-0.5,0.5-0.5,0.9l0,1.8c0,0.4,0.2,0.7,0.5,0.9l1.5,0.9
c0.2,0.1,0.3,0.1,0.5,0.1c0.2,0,0.4,0,0.5-0.1l1.5-0.9c0.3-0.2,0.5-0.5,0.5-0.9l0-1.8C27.8,16.6,27.6,16.2,27.3,16z"/>
<path d="M31.2,25.1L30,23.6c-0.2-0.3-0.5-0.4-0.8-0.4l-7.9,0c0,0,0,0,0,0c-0.3,0-0.6,0.1-0.8,0.4l-1.2,1.5c-0.3,0.4-0.3,0.9,0,1.3
l1.2,1.5c0.2,0.3,0.5,0.4,0.8,0.4l7.9,0c0,0,0,0,0,0c0.3,0,0.6-0.1,0.8-0.4l1.2-1.5C31.5,26,31.5,25.5,31.2,25.1z"/>
</g>
<g>
<path d="M19.2,35.6V42H18v-2.7h-2.9V42h-1.2v-6.4h1.2v2.6H18v-2.6H19.2z"/>
<path d="M25.4,41c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7c0.1-0.3,0.1-0.5,0.1-0.8v-3.4h1.3v3.4
c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-1-0.1-1.4-0.3
c-0.4-0.2-0.7-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.2-0.8-0.2-1.2v-3.4h1.3v3.4c0,0.3,0,0.5,0.1,0.8
c0.1,0.3,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.4,0.5,0.5C24.9,41,25.1,41,25.4,41z"/>
<path d="M31.7,42v-6.4H34c0.5,0,1,0.1,1.4,0.3c0.4,0.2,0.7,0.4,1,0.7c0.3,0.3,0.5,0.6,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2
c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.6C34.9,41.9,34.5,42,34,42H31.7z M35.9,38.8
c0-0.3,0-0.6-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.3-0.6-0.4c-0.2-0.1-0.5-0.2-0.8-0.2h-1.1v4.2H34
c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.6-0.4c0.2-0.2,0.3-0.4,0.4-0.7C35.8,39.3,35.9,39.1,35.9,38.8z"/>
</g>
</svg>

After

(image error) Size: 8 KiB

View file

@ -20,9 +20,10 @@ Window {
objectName: "AddressBarDialog"
frame: HiddenFrame {}
hideBackground: true
visible: false
destroyOnInvisible: false
shown: false
destroyOnHidden: false
resizable: false
scale: 1.25 // Make this dialog a little larger than normal
@ -145,14 +146,14 @@ Window {
if (addressLine.text !== "") {
addressBarDialog.loadAddress(addressLine.text)
}
root.visible = false;
root.shown = false;
}
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Escape:
case Qt.Key_Back:
root.visible = false
root.shown = false
event.accepted = true
break
case Qt.Key_Enter:

View file

@ -15,15 +15,15 @@ import Qt.labs.settings 1.0
import "styles-uit"
import "controls-uit" as HifiControls
import "windows-uit"
import "windows"
import "dialogs"
Window {
ScrollingWindow {
id: root
objectName: "AssetServer"
title: "Asset Browser"
resizable: true
destroyOnInvisible: true
destroyOnHidden: true
implicitWidth: 384; implicitHeight: 640
minSize: Qt.vector2d(200, 300)

View file

@ -2,22 +2,24 @@ import QtQuick 2.3
import QtQuick.Controls 1.2
import QtWebEngine 1.1
import "controls"
import "styles"
import "controls-uit"
import "styles-uit"
import "windows"
Window {
ScrollingWindow {
id: root
HifiConstants { id: hifi }
title: "Browser"
resizable: true
destroyOnInvisible: true
destroyOnHidden: true
width: 800
height: 600
property alias webView: webview
x: 100
y: 100
Component.onCompleted: {
visible = true
shown = true
addressBar.text = webview.url
}
@ -30,15 +32,9 @@ Window {
Item {
id:item
anchors.fill: parent
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: webview.top
color: "white"
}
width: pane.contentWidth
implicitHeight: pane.scrollHeight
Row {
id: buttons
spacing: 4
@ -46,25 +42,37 @@ Window {
anchors.topMargin: 8
anchors.left: parent.left
anchors.leftMargin: 8
FontAwesome {
id: back; text: "\uf0a8"; size: 48; enabled: webview.canGoBack;
HiFiGlyphs {
id: back;
enabled: webview.canGoBack;
text: hifi.glyphs.backward
color: enabled ? hifi.colors.text : hifi.colors.disabledText
size: 48
MouseArea { anchors.fill: parent; onClicked: webview.goBack() }
}
FontAwesome {
id: forward; text: "\uf0a9"; size: 48; enabled: webview.canGoForward;
HiFiGlyphs {
id: forward;
enabled: webview.canGoForward;
text: hifi.glyphs.forward
color: enabled ? hifi.colors.text : hifi.colors.disabledText
MouseArea { anchors.fill: parent; onClicked: webview.goBack() }
size: 48
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
}
FontAwesome {
id: reload; size: 48; text: webview.loading ? "\uf057" : "\uf021"
MouseArea { anchors.fill: parent; onClicked: webview.loading ? webview.stop() : webview.reload() }
HiFiGlyphs {
id: reload;
enabled: webview.canGoForward;
text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload
color: enabled ? hifi.colors.text : hifi.colors.disabledText
size: 48
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
}
}
Border {
Item {
id: border
height: 48
radius: 8
anchors.top: parent.top
anchors.topMargin: 8
anchors.right: parent.right
@ -86,15 +94,18 @@ Window {
onSourceChanged: console.log("Icon url: " + source)
}
}
TextInput {
TextField {
id: addressBar
anchors.right: parent.right
anchors.rightMargin: 8
anchors.left: barIcon.right
anchors.leftMargin: 0
anchors.verticalCenter: parent.verticalCenter
focus: true
colorScheme: hifi.colorSchemes.dark
placeholderText: "Enter URL"
Component.onCompleted: scriptsModel.filterRegExp = new RegExp("^.*$", "i")
Keys.onPressed: {
switch(event.key) {
case Qt.Key_Enter:
@ -110,7 +121,7 @@ Window {
}
}
WebView {
WebEngineView {
id: webview
url: "http://highfidelity.com"
anchors.top: buttons.bottom
@ -119,7 +130,7 @@ Window {
anchors.left: parent.left
anchors.right: parent.right
onLoadingChanged: {
if (loadRequest.status == WebEngineView.LoadSucceededStatus) {
if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
addressBar.text = loadRequest.url
}
}
@ -127,7 +138,7 @@ Window {
console.log("New icon: " + icon)
}
profile: desktop.browserProfile
//profile: desktop.browserProfile
}

View file

@ -12,9 +12,9 @@ import QtQuick 2.5
import Hifi 1.0 as Hifi
import "controls-uit"
import "windows-uit" as Windows
import "windows" as Windows
Windows.Window {
Windows.ScrollingWindow {
id: root
width: 800
height: 800

View file

@ -14,7 +14,7 @@ import "controls"
import "styles"
import "windows"
Window {
ScrollingWindow {
id: root
HifiConstants { id: hifi }
objectName: "LoginDialog"
@ -22,8 +22,9 @@ Window {
width: loginDialog.implicitWidth
// FIXME make movable
anchors.centerIn: parent
destroyOnInvisible: false
visible: false
destroyOnHidden: false
hideBackground: true
shown: false
LoginDialog {
id: loginDialog
@ -268,8 +269,8 @@ Window {
}
}
onVisibleChanged: {
if (!visible) {
onShownChanged: {
if (!shown) {
username.text = ""
password.text = ""
loginDialog.statusText = ""
@ -282,7 +283,7 @@ Window {
switch (event.key) {
case Qt.Key_Escape:
case Qt.Key_Back:
root.visible = false;
root.shown = false;
event.accepted = true;
break;

View file

@ -13,16 +13,16 @@ import QtQuick.Controls 1.4
import QtWebEngine 1.1
import QtWebChannel 1.0
import "windows-uit" as Windows
import "windows" as Windows
import "controls-uit" as Controls
import "styles-uit"
Windows.Window {
Windows.ScrollingWindow {
id: root
HifiConstants { id: hifi }
title: "WebWindow"
resizable: true
visible: false
shown: false
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false
property alias source: webview.url

View file

@ -14,7 +14,7 @@ Windows.Window {
HifiConstants { id: hifi }
title: "QmlWindow"
resizable: true
visible: false
shown: false
focus: true
property var channel;
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer

View file

@ -15,18 +15,18 @@ import QtWebEngine 1.1
import QtWebChannel 1.0
import Qt.labs.settings 1.0
import "windows-uit"
import "windows"
import "controls-uit"
import "styles-uit"
Window {
ScrollingWindow {
id: toolWindow
resizable: true
objectName: "ToolWindow"
destroyOnCloseButton: false
destroyOnInvisible: false
destroyOnHidden: false
closable: true
visible: false
shown: false
title: "Edit"
property alias tabView: tabView
implicitWidth: 520; implicitHeight: 695
@ -142,7 +142,7 @@ Window {
return;
}
}
visible = false;
shown = false;
}
function findIndexForUrl(source) {
@ -172,7 +172,7 @@ Window {
var tab = tabView.getTab(index);
if (newVisible) {
toolWindow.visible = true
toolWindow.shown = true
tab.enabled = true
} else {
tab.enabled = false;

View file

@ -3,11 +3,11 @@ import QtQuick 2.3
import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.3
import QtGraphicalEffects 1.0
import "controls"
import "styles"
import "controls-uit"
import "styles-uit"
import "windows"
Window {
ScrollingWindow {
id: root
HifiConstants { id: hifi }
objectName: "UpdateDialog"

View file

@ -15,7 +15,7 @@ import QtQuick.XmlListModel 2.0
import "../styles-uit"
import "../controls-uit" as HifiControls
import "../windows-uit"
import "../windows"
import "../hifi/models"
TableView {

View file

@ -52,6 +52,7 @@ FocusScope {
readonly property real menu: 8000
}
QtObject {
id: d
@ -64,7 +65,7 @@ FocusScope {
var oldChildren = expectedChildren;
var newChildren = d.getRepositionChildren();
if (oldRecommendedRect != Qt.rect(0,0,0,0)
if (oldRecommendedRect != Qt.rect(0,0,0,0) && oldRecommendedRect != Qt.rect(0,0,1,1)
&& (oldRecommendedRect != newRecommendedRect
|| oldChildren != newChildren)
) {
@ -93,6 +94,17 @@ FocusScope {
return item;
}
function findMatchingChildren(item, predicate) {
var results = [];
for (var i in item.children) {
var child = item.children[i];
if (predicate(child)) {
results.push(child);
}
}
return results;
}
function isTopLevelWindow(item) {
return item.topLevelWindow;
}
@ -106,19 +118,9 @@ FocusScope {
}
function getTopLevelWindows(predicate) {
var currentWindows = [];
if (!desktop) {
console.log("Could not find desktop for " + item)
return currentWindows;
}
for (var i = 0; i < desktop.children.length; ++i) {
var child = desktop.children[i];
if (isTopLevelWindow(child) && (!predicate || predicate(child))) {
currentWindows.push(child)
}
}
return currentWindows;
return findMatchingChildren(desktop, function(child) {
return (isTopLevelWindow(child) && (!predicate || predicate(child)));
});
}
function getDesktopWindow(item) {
@ -227,19 +229,9 @@ FocusScope {
}
function getRepositionChildren(predicate) {
var currentWindows = [];
if (!desktop) {
console.log("Could not find desktop");
return currentWindows;
}
for (var i = 0; i < desktop.children.length; ++i) {
var child = desktop.children[i];
if (child.shouldReposition === true && (!predicate || predicate(child))) {
currentWindows.push(child)
}
}
return currentWindows;
return findMatchingChildren(desktop, function(child) {
return (child.shouldReposition === true && (!predicate || predicate(child)));
});
}
function repositionAll() {
@ -265,6 +257,63 @@ FocusScope {
}
}
property bool pinned: false
property var hiddenChildren: []
function togglePinned() {
pinned = !pinned
}
function setPinned(newPinned) {
pinned = newPinned
}
property real unpinnedAlpha: 1.0;
Behavior on unpinnedAlpha {
NumberAnimation {
easing.type: Easing.Linear;
duration: 300
}
}
state: "NORMAL"
states: [
State {
name: "NORMAL"
PropertyChanges { target: desktop; unpinnedAlpha: 1.0 }
},
State {
name: "PINNED"
PropertyChanges { target: desktop; unpinnedAlpha: 0.0 }
}
]
transitions: [
Transition {
NumberAnimation { properties: "unpinnedAlpha"; duration: 300 }
}
]
onPinnedChanged: {
if (pinned) {
nullFocus.focus = true;
nullFocus.forceActiveFocus();
// recalculate our non-pinned children
hiddenChildren = d.findMatchingChildren(desktop, function(child){
return !d.isTopLevelWindow(child) && child.visible && !child.pinned;
});
hiddenChildren.forEach(function(child){
child.opacity = Qt.binding(function(){ return desktop.unpinnedAlpha });
});
}
state = pinned ? "PINNED" : "NORMAL"
}
onShowDesktop: pinned = false
function raise(item) {
var targetWindow = d.getDesktopWindow(item);
if (!targetWindow) {
@ -422,7 +471,6 @@ FocusScope {
event.accepted = false;
}
function unfocusWindows() {
var windows = d.getTopLevelWindows();
for (var i = 0; i < windows.length; ++i) {
@ -433,6 +481,8 @@ FocusScope {
FocusHack { id: focusHack; }
FocusScope { id: nullFocus; }
Rectangle {
id: focusDebugger;
objectName: "focusDebugger"

View file

@ -18,7 +18,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs
import ".."
import "../controls-uit"
import "../styles-uit"
import "../windows-uit"
import "../windows"
import "fileDialog"
@ -729,7 +729,7 @@ ModalWindow {
Action {
id: cancelAction
text: "Cancel"
onTriggered: { canceled(); root.visible = false; }
onTriggered: { canceled(); root.shown = false; }
}
}

View file

@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs
import "../controls-uit"
import "../styles-uit"
import "../windows-uit"
import "../windows"
import "messageDialog"
@ -24,7 +24,7 @@ ModalWindow {
implicitWidth: 640
implicitHeight: 320
destroyOnCloseButton: true
destroyOnInvisible: true
destroyOnHidden: true
visible: true
signal selected(int button);

View file

@ -13,14 +13,14 @@ import QtQuick.Controls 1.4
import "../controls-uit" as HifiControls
import "../styles-uit"
import "../windows-uit"
import "../windows"
import "preferences"
Window {
ScrollingWindow {
id: root
title: "Preferences"
resizable: true
destroyOnInvisible: true
destroyOnHidden: true
width: 500
height: 577
property var sections: []

View file

@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs
import "../controls-uit"
import "../styles-uit"
import "../windows-uit"
import "../windows"
ModalWindow {
id: root

View file

@ -12,7 +12,7 @@ import QtQuick 2.5
import QtQuick.Controls 1.4
import QtWebEngine 1.1
import "../../windows-uit" as Windows
import "../../windows" as Windows
import "../../controls-uit" as Controls
import "../../styles-uit"
@ -23,15 +23,10 @@ Windows.Window {
resizable: true
modality: Qt.ApplicationModal
Item {
width: pane.contentWidth
implicitHeight: pane.scrollHeight
Controls.WebView {
id: webview
anchors.fill: parent
url: "https://metaverse.highfidelity.com/marketplace?category=avatars"
focus: true
}
Controls.WebView {
id: webview
anchors.fill: parent
url: "https://metaverse.highfidelity.com/marketplace?category=avatars"
focus: true
}
}

View file

@ -1,14 +1,18 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtWebEngine 1.1;
import Qt.labs.settings 1.0
import "../desktop"
import "../desktop" as OriginalDesktop
import ".."
import "."
import "./toolbars"
Desktop {
OriginalDesktop.Desktop {
id: desktop
MouseArea {
id: hoverWatch
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
@ -18,13 +22,6 @@ Desktop {
acceptedButtons: Qt.NoButton
}
Component.onCompleted: {
WebEngine.settings.javascriptCanOpenWindows = true;
WebEngine.settings.javascriptCanAccessClipboard = false;
WebEngine.settings.spatialNavigationEnabled = false;
WebEngine.settings.localContentCanAccessRemoteUrls = true;
}
// The tool window, one instance
property alias toolWindow: toolWindow
ToolWindow { id: toolWindow }
@ -47,7 +44,42 @@ Desktop {
}
}
property var toolbars: ({})
Component { id: toolbarBuilder; Toolbar { } }
Component.onCompleted: {
WebEngine.settings.javascriptCanOpenWindows = true;
WebEngine.settings.javascriptCanAccessClipboard = false;
WebEngine.settings.spatialNavigationEnabled = false;
WebEngine.settings.localContentCanAccessRemoteUrls = true;
var sysToolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system");
var toggleHudButton = sysToolbar.addButton({
objectName: "hudToggle",
imageURL: "../../../icons/hud-01.svg",
visible: true,
pinned: true,
});
toggleHudButton.yOffset = Qt.binding(function(){
return desktop.pinned ? 50 : 0
});
toggleHudButton.clicked.connect(function(){
console.log("Clicked on hud button")
var overlayMenuItem = "Overlays"
MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem));
});
}
// Create or fetch a toolbar with the given name
function getToolbar(name) {
var result = toolbars[name];
if (!result) {
result = toolbars[name] = toolbarBuilder.createObject(desktop, {});
result.objectName = name;
}
return result;
}
}

View file

@ -6,17 +6,17 @@ import QtQuick.Controls.Styles 1.4
import "../../styles-uit"
import "../../controls-uit" as HifiControls
import "../../windows-uit"
import "../../windows"
import "attachments"
Window {
ScrollingWindow {
id: root
title: "Attachments"
objectName: "AttachmentsDialog"
width: 600
height: 600
resizable: true
destroyOnInvisible: true
destroyOnHidden: true
minSize: Qt.vector2d(400, 500)
HifiConstants { id: hifi }

View file

@ -9,9 +9,9 @@ import "../models"
import "../../styles-uit"
import "../../controls-uit" as HifiControls
import "../../windows-uit"
import "../../windows"
Window {
ScrollingWindow {
id: root
resizable: true
width: 600

View file

@ -15,14 +15,14 @@ import Qt.labs.settings 1.0
import "../../styles-uit"
import "../../controls-uit" as HifiControls
import "../../windows-uit"
import "../../windows"
Window {
ScrollingWindow {
id: root
objectName: "RunningScripts"
title: "Running Scripts"
resizable: true
destroyOnInvisible: true
destroyOnHidden: true
implicitWidth: 424
implicitHeight: isHMD ? 695 : 728
minSize: Qt.vector2d(424, 300)
@ -34,6 +34,9 @@ Window {
property var runningScriptsModel: ListModel { }
property bool isHMD: false
onVisibleChanged: console.log("Running scripts visible changed to " + visible)
onShownChanged: console.log("Running scripts visible changed to " + visible)
Settings {
category: "Overlay.RunningScripts"
property alias x: root.x

View file

@ -7,7 +7,7 @@ import "../../windows"
import "../../js/Utils.js" as Utils
import "../models"
Window {
ScrollingWindow {
id: root
resizable: true
width: 516

View file

@ -8,7 +8,7 @@ import "."
import ".."
import "../../../styles-uit"
import "../../../controls-uit" as HifiControls
import "../../../windows-uit"
import "../../../windows"
Item {
height: column.height + 2 * 8

View file

@ -3,7 +3,7 @@ import QtQuick.Controls 1.4
import "../../../styles-uit"
import "../../../controls-uit" as HifiControls
import "../../../windows-uit"
import "../../../windows"
Item {
id: root

View file

@ -0,0 +1,151 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import Qt.labs.settings 1.0
import "../../windows"
import "."
Window {
id: window
frame: ToolFrame {
horizontalSpacers: horizontal
verticalSpacers: !horizontal
}
hideBackground: true
resizable: false
destroyOnCloseButton: false
destroyOnHidden: false
closable: false
shown: true
width: content.width
height: content.height
visible: true
// Disable this window from being able to call 'desktop.raise() and desktop.showDesktop'
activator: Item {}
property bool horizontal: true
property real buttonSize: 50;
property var buttons: []
property var container: horizontal ? row : column
Settings {
category: "toolbar/" + window.objectName
property alias x: window.x
property alias y: window.y
}
onHorizontalChanged: {
var newParent = horizontal ? row : column;
for (var i in buttons) {
var child = buttons[i];
child.parent = newParent;
if (horizontal) {
child.y = 0
} else {
child.x = 0
}
}
}
Item {
id: content
implicitHeight: horizontal ? row.height : column.height
implicitWidth: horizontal ? row.width : column.width
Row {
id: row
spacing: 6
}
Column {
id: column
spacing: 6
}
Component { id: toolbarButtonBuilder; ToolbarButton { } }
Connections {
target: desktop
onPinnedChanged: {
if (!window.pinned) {
return;
}
var newPinned = desktop.pinned;
for (var i in buttons) {
var child = buttons[i];
if (desktop.pinned) {
if (!child.pinned) {
child.visible = false;
}
} else {
child.visible = true;
}
}
}
}
}
function findButtonIndex(name) {
if (!name) {
return -1;
}
for (var i in buttons) {
var child = buttons[i];
if (child.objectName === name) {
return i;
}
}
return -1;
}
function findButton(name) {
var index = findButtonIndex(name);
if (index < 0) {
return;
}
return buttons[index];
}
function addButton(properties) {
properties = properties || {}
// If a name is specified, then check if there's an existing button with that name
// and return it if so. This will allow multiple clients to listen to a single button,
// and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded
var result = findButton(properties.objectName);
if (result) {
return result;
}
properties.toolbar = this;
properties.opacity = 0;
result = toolbarButtonBuilder.createObject(container, properties);
buttons.push(result);
result.opacity = 1;
updatePinned();
return result;
}
function removeButton(name) {
var index = findButtonIndex(name);
if (index < -1) {
console.warn("Tried to remove non-existent button " + name);
return;
}
buttons[index].destroy();
buttons.splice(index, 1);
updatePinned();
}
function updatePinned() {
var newPinned = false;
for (var i in buttons) {
var child = buttons[i];
if (child.pinned) {
newPinned = true;
break;
}
}
pinned = newPinned;
}
}

View file

@ -0,0 +1,65 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
Item {
id: button
property alias imageURL: image.source
property alias alpha: button.opacity
property var subImage;
property int yOffset: 0
property int buttonState: 0
property var toolbar;
property real size: 50 // toolbar ? toolbar.buttonSize : 50
width: size; height: size
property bool pinned: false
clip: true
Behavior on opacity {
NumberAnimation {
duration: 150
easing.type: Easing.InOutCubic
}
}
property alias fadeTargetProperty: button.opacity
onFadeTargetPropertyChanged: {
visible = (fadeTargetProperty !== 0.0);
}
onVisibleChanged: {
if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) {
var target = visible;
visible = !visible;
fadeTargetProperty = target ? 1.0 : 0.0;
return;
}
}
onButtonStateChanged: {
yOffset = size * buttonState
}
Component.onCompleted: {
if (subImage) {
if (subImage.y) {
yOffset = subImage.y;
}
}
}
signal clicked()
Image {
id: image
y: -button.yOffset;
width: parent.width
}
MouseArea {
anchors.fill: parent
onClicked: button.clicked();
}
}

View file

@ -1,119 +0,0 @@
//
// DefaultFrame.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtGraphicalEffects 1.0
import "."
import "../styles-uit"
Frame {
HifiConstants { id: hifi }
Rectangle {
// Dialog frame
id: frameContent
readonly property int iconSize: hifi.dimensions.frameIconSize
readonly property int frameMargin: 9
readonly property int frameMarginLeft: frameMargin
readonly property int frameMarginRight: frameMargin
readonly property int frameMarginTop: 2 * frameMargin + iconSize
readonly property int frameMarginBottom: iconSize + 11
anchors {
topMargin: -frameMarginTop
leftMargin: -frameMarginLeft
rightMargin: -frameMarginRight
bottomMargin: -frameMarginBottom
}
anchors.fill: parent
color: hifi.colors.baseGrayHighlight40
border {
width: hifi.dimensions.borderWidth
color: hifi.colors.faintGray50
}
radius: hifi.dimensions.borderRadius
// Enable dragging of the window
MouseArea {
anchors.fill: parent
drag.target: window
}
Row {
id: controlsRow
anchors {
right: parent.right;
top: parent.top;
topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title
rightMargin: frameContent.frameMarginRight;
}
spacing: frameContent.iconSize / 4
HiFiGlyphs {
// "Pin" button
visible: false
text: (frame.pinned && !pinClickArea.containsMouse) || (!frame.pinned && pinClickArea.containsMouse) ? hifi.glyphs.pinInverted : hifi.glyphs.pin
color: pinClickArea.containsMouse && !pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white
size: frameContent.iconSize
MouseArea {
id: pinClickArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
onClicked: { frame.pin(); mouse.accepted = false; }
}
}
HiFiGlyphs {
// "Close" button
visible: window ? window.closable : false
text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close
color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white
size: frameContent.iconSize
MouseArea {
id: closeClickArea
anchors.fill: parent
hoverEnabled: true
onClicked: window.visible = false;
}
}
}
RalewayRegular {
// Title
id: titleText
anchors {
left: parent.left
leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x
right: controlsRow.left
rightMargin: frameContent.iconSize
top: parent.top
topMargin: frameContent.frameMargin
}
text: window ? window.title : ""
color: hifi.colors.white
size: hifi.fontSizes.overlayTitle
}
DropShadow {
source: titleText
anchors.fill: titleText
horizontalOffset: 2
verticalOffset: 2
samples: 2
color: hifi.colors.baseGrayShadow60
visible: (window && window.focus)
cached: true
}
}
}

View file

@ -1,60 +0,0 @@
//
// Fadable.qml
//
// Created by Bradley Austin Davis on 15 Jan 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtGraphicalEffects 1.0
import "../styles-uit"
// Enable window visibility transitions
FocusScope {
id: root
HifiConstants { id: hifi }
Component.onCompleted: {
fadeTargetProperty = visible ? 1.0 : 0.0
}
// The target property to animate, usually scale or opacity
property alias fadeTargetProperty: root.opacity
// always start the property at 0 to enable fade in on creation
fadeTargetProperty: 0
// DO NOT set visible to false or when derived types override it it
// will short circuit the fade in on initial visibility
// visible: false <--- NO
// Some dialogs should be destroyed when they become
// invisible, so handle that
onVisibleChanged: {
// If someone directly set the visibility to false
// toggle it back on and use the targetVisible flag to transition
// via fading.
if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) {
var target = visible;
visible = !visible;
fadeTargetProperty = target ? 1.0 : 0.0;
return;
}
}
// The actual animator
Behavior on fadeTargetProperty {
NumberAnimation {
duration: hifi.effects.fadeInDuration
easing.type: Easing.InOutCubic
}
}
// Once we're transparent, disable the dialog's visibility
onFadeTargetPropertyChanged: {
visible = (fadeTargetProperty != 0.0);
}
}

View file

@ -1,133 +0,0 @@
//
// Frame.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtGraphicalEffects 1.0
import "../styles-uit"
import "../js/Utils.js" as Utils
Item {
id: frame
HifiConstants { id: hifi }
default property var decoration
property bool gradientsSupported: desktop.gradientsSupported
readonly property int frameMarginLeft: frameContent.frameMarginLeft
readonly property int frameMarginRight: frameContent.frameMarginRight
readonly property int frameMarginTop: frameContent.frameMarginTop
readonly property int frameMarginBottom: frameContent.frameMarginBottom
// Frames always fill their parents, but their decorations may extend
// beyond the window via negative margin sizes
anchors.fill: parent
children: [
focusShadow,
decoration,
sizeOutline,
debugZ,
sizeDrag
]
Text {
id: debugZ
visible: DebugQML
text: window ? "Z: " + window.z : ""
y: window ? window.height + 4 : 0
}
function deltaSize(dx, dy) {
var newSize = Qt.vector2d(window.width + dx, window.height + dy);
newSize = Utils.clampVector(newSize, window.minSize, window.maxSize);
window.width = newSize.x
window.height = newSize.y
}
RadialGradient {
id: focusShadow
width: 1.66 * window.width
height: 1.66 * window.height
x: (window.width - width) / 2
y: window.height / 2 - 0.375 * height
visible: gradientsSupported && window && window.focus && pane.visible
gradient: Gradient {
// GradientStop position 0.5 is at full circumference of circle that fits inside the square.
GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity
GradientStop { position: 0.333; color: "#1f000000" } // black, 12% opacity
GradientStop { position: 0.5; color: "#00000000" } // black, 0% opacity
GradientStop { position: 1.0; color: "#00000000" }
}
cached: true
}
Rectangle {
id: sizeOutline
x: -frameMarginLeft
y: -frameMarginTop
width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0
height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0
color: hifi.colors.baseGrayHighlight15
border.width: 3
border.color: hifi.colors.white50
radius: hifi.dimensions.borderRadius
visible: window ? !pane.visible : false
}
MouseArea {
// Resize handle
id: sizeDrag
width: hifi.dimensions.frameIconSize
height: hifi.dimensions.frameIconSize
enabled: window ? window.resizable : false
hoverEnabled: true
x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0
y: window ? window.height + 4 : 0
property vector2d pressOrigin
property vector2d sizeOrigin
property bool hid: false
onPressed: {
//console.log("Pressed on size")
pressOrigin = Qt.vector2d(mouseX, mouseY)
sizeOrigin = Qt.vector2d(window.content.width, window.content.height)
hid = false;
}
onReleased: {
if (hid) {
pane.visible = true
frameContent.visible = true
hid = false;
}
}
onPositionChanged: {
if (pressed) {
if (pane.visible) {
pane.visible = false;
frameContent.visible = false
hid = true;
}
var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin);
frame.deltaSize(delta.x, delta.y)
}
}
HiFiGlyphs {
visible: sizeDrag.enabled
x: -11 // Move a little to visually align
y: window.modality == Qt.ApplicationModal ? -6 : -4
text: hifi.glyphs.resizeHandle
size: hifi.dimensions.frameIconSize + 10
color: sizeDrag.containsMouse || sizeDrag.pressed
? hifi.colors.white
: (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80)
}
}
}

View file

@ -1,98 +0,0 @@
//
// ModalFrame.qml
//
// Created by Bradley Austin Davis on 15 Jan 2016
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import "."
import "../controls-uit"
import "../styles-uit"
Frame {
HifiConstants { id: hifi }
Rectangle {
id: frameContent
readonly property bool hasTitle: window.title != ""
readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x
readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x
readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0)
readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y
signal frameClicked();
anchors {
fill: parent
topMargin: -frameMarginTop
leftMargin: -frameMarginLeft
rightMargin: -frameMarginRight
bottomMargin: -frameMarginBottom
}
border {
width: hifi.dimensions.borderWidth
color: hifi.colors.lightGrayText80
}
radius: hifi.dimensions.borderRadius
color: hifi.colors.faintGray
// Enable dragging of the window
MouseArea {
anchors.fill: parent
drag.target: window
enabled: window.draggable
onClicked: window.frameClicked();
}
Item {
visible: frameContent.hasTitle
anchors.fill: parent
anchors {
topMargin: -parent.anchors.topMargin
leftMargin: -parent.anchors.leftMargin
rightMargin: -parent.anchors.rightMargin
}
Item {
width: title.width + (icon.text !== "" ? icon.width + hifi.dimensions.contentSpacing.x : 0)
x: (parent.width - width) / 2
onWidthChanged: window.titleWidth = width
HiFiGlyphs {
id: icon
text: window.iconText ? window.iconText : ""
size: window.iconSize ? window.iconSize : 30
color: hifi.colors.lightGray
visible: text != ""
anchors.verticalCenter: title.verticalCenter
anchors.left: parent.left
}
RalewayRegular {
id: title
text: window.title
elide: Text.ElideRight
color: hifi.colors.baseGrayHighlight
size: hifi.fontSizes.overlayTitle
y: -hifi.dimensions.modalDialogTitleHeight
anchors.right: parent.right
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: hifi.colors.lightGray
}
}
}
}

View file

@ -1,28 +0,0 @@
//
// ModalWindow.qml
//
// Created by Bradley Austin Davis on 22 Jan 2016
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import "."
Window {
id: window
modality: Qt.ApplicationModal
destroyOnCloseButton: true
destroyOnInvisible: true
frame: ModalFrame { }
property int colorScheme: hifi.colorSchemes.light
property bool draggable: false
signal frameClicked();
anchors.centerIn: draggable ? undefined : parent
}

View file

@ -1,343 +0,0 @@
//
// Window.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
import "."
import "../styles-uit"
// FIXME how do I set the initial position of a window without
// overriding places where the a individual client of the window
// might be setting the position with a Settings{} element?
// FIXME how to I enable dragging without allowing the window to lay outside
// of the desktop? How do I ensure when the desktop resizes all the windows
// are still at least partially visible?
Fadable {
id: window
HifiConstants { id: hifi }
// The Window size is the size of the content, while the frame
// decorations can extend outside it.
implicitHeight: content ? content.height : 0
implicitWidth: content ? content.width : 0
x: desktop.invalid_position; y: desktop.invalid_position;
enabled: visible
signal windowDestroyed();
property int modality: Qt.NonModal
readonly property bool topLevelWindow: true
property string title
// Should the window be closable control?
property bool closable: true
// Should the window try to remain on top of other windows?
property bool alwaysOnTop: false
// Should hitting the close button hide or destroy the window?
property bool destroyOnCloseButton: true
// Should hiding the window destroy it or just hide it?
property bool destroyOnInvisible: false
// FIXME support for pinned / unpinned pending full design
// property bool pinnable: false
// property bool pinned: false
property bool resizable: false
property bool gradientsSupported: desktop.gradientsSupported
property int colorScheme: hifi.colorSchemes.dark
property vector2d minSize: Qt.vector2d(100, 100)
property vector2d maxSize: Qt.vector2d(1280, 800)
// The content to place inside the window, determined by the client
default property var content
property var footer: Item { } // Optional static footer at the bottom of the dialog.
function setDefaultFocus() {} // Default function; can be overridden by dialogs.
property var rectifier: Timer {
property bool executing: false;
interval: 100
repeat: false
running: false
onTriggered: {
executing = true;
x = Math.floor(x);
y = Math.floor(y);
executing = false;
}
function begin() {
if (!executing) {
restart();
}
}
}
onXChanged: rectifier.begin();
onYChanged: rectifier.begin();
// This mouse area serves to raise the window. To function, it must live
// in the window and have a higher Z-order than the content, but follow
// the position and size of frame decoration
property var activator: MouseArea {
width: frame.decoration.width
height: frame.decoration.height
x: frame.decoration.anchors.leftMargin
y: frame.decoration.anchors.topMargin
propagateComposedEvents: true
acceptedButtons: Qt.AllButtons
enabled: window.visible
onPressed: {
//console.log("Pressed on activator area");
window.raise();
mouse.accepted = false;
}
}
// This mouse area serves to swallow mouse events while the mouse is over the window
// to prevent things like mouse wheel events from reaching the application and changing
// the camera if the user is scrolling through a list and gets to the end.
property var swallower: MouseArea {
width: frame.decoration.width
height: frame.decoration.height
x: frame.decoration.anchors.leftMargin
y: frame.decoration.anchors.topMargin
hoverEnabled: true
acceptedButtons: Qt.AllButtons
enabled: window.visible
onClicked: {}
onDoubleClicked: {}
onPressAndHold: {}
onReleased: {}
onWheel: {}
}
// Default to a standard frame. Can be overriden to provide custom
// frame styles, like a full desktop frame to simulate a modal window
property var frame: DefaultFrame { }
// Scrollable window content.
property var pane: Item {
property bool isScrolling: scrollView.height < scrollView.contentItem.height
property int contentWidth: scrollView.width - (isScrolling ? 10 : 0)
property int scrollHeight: scrollView.height
anchors.fill: parent
anchors.rightMargin: isScrolling ? 11 : 0
Rectangle {
id: contentBackground
anchors.fill: parent
anchors.rightMargin: parent.isScrolling ? 11 : 0
color: hifi.colors.baseGray
visible: modality != Qt.ApplicationModal
}
LinearGradient {
visible: gradientsSupported && modality != Qt.ApplicationModal
anchors.top: contentBackground.bottom
anchors.left: contentBackground.left
width: contentBackground.width - 1
height: 4
start: Qt.point(0, 0)
end: Qt.point(0, 4)
gradient: Gradient {
GradientStop { position: 0.0; color: hifi.colors.darkGray }
GradientStop { position: 1.0; color: hifi.colors.darkGray0 }
}
cached: true
}
ScrollView {
id: scrollView
contentItem: content
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
anchors.fill: parent
anchors.rightMargin: parent.isScrolling ? 1 : 0
anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0
style: ScrollViewStyle {
padding.right: -7 // Move to right away from content.
handle: Item {
implicitWidth: 8
Rectangle {
radius: 4
color: hifi.colors.white30
anchors {
fill: parent
leftMargin: 2 // Finesse size and position.
topMargin: 1
bottomMargin: 1
}
}
}
scrollBarBackground: Item {
implicitWidth: 10
Rectangle {
color: hifi.colors.darkGray30
radius: 4
anchors {
fill: parent
topMargin: -1 // Finesse size
bottomMargin: -2
}
}
}
incrementControl: Item {
visible: false
}
decrementControl: Item {
visible: false
}
}
}
Rectangle {
// Optional non-scrolling footer.
id: footerPane
anchors {
left: parent.left
bottom: parent.bottom
}
width: parent.contentWidth
height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3
color: hifi.colors.baseGray
visible: footer.height > 0
Item {
// Horizontal rule.
anchors.fill: parent
Rectangle {
width: parent.width
height: 1
y: 1 // Stop displaying content just above horizontal rule/=.
color: hifi.colors.baseGrayShadow
}
Rectangle {
width: parent.width
height: 1
y: 2
color: hifi.colors.baseGrayHighlight
}
}
Item {
anchors.fill: parent
anchors.topMargin: 3 // Horizontal rule.
children: [ footer ]
}
}
}
children: [ swallower, frame, pane, activator ]
Component.onCompleted: {
window.parentChanged.connect(raise);
raise();
setDefaultFocus();
centerOrReposition();
}
Component.onDestruction: {
window.parentChanged.disconnect(raise); // Prevent warning on shutdown
windowDestroyed();
}
onVisibleChanged: {
if (!visible && destroyOnInvisible) {
destroy();
return;
}
if (visible) {
raise();
}
enabled = visible
if (visible && parent) {
centerOrReposition();
}
}
function centerOrReposition() {
if (x == desktop.invalid_position && y == desktop.invalid_position) {
desktop.centerOnVisible(window);
} else {
desktop.repositionOnVisible(window);
}
}
function raise() {
if (visible && parent) {
desktop.raise(window)
}
}
function pin() {
// pinned = ! pinned
}
// our close function performs the same way as the OffscreenUI class:
// don't do anything but manipulate the targetVisible flag and let the other
// mechanisms decide if the window should be destroyed after the close
// animation completes
// FIXME using this close function messes up the visibility signals received by the
// type and it's derived types
// function close() {
// console.log("Closing " + window)
// if (destroyOnCloseButton) {
// destroyOnInvisible = true
// }
// visible = false;
// }
function framedRect() {
if (!frame || !frame.decoration) {
return Qt.rect(0, 0, window.width, window.height)
}
return Qt.rect(frame.decoration.anchors.leftMargin, frame.decoration.anchors.topMargin,
window.width - frame.decoration.anchors.leftMargin - frame.decoration.anchors.rightMargin,
window.height - frame.decoration.anchors.topMargin - frame.decoration.anchors.bottomMargin)
}
Keys.onPressed: {
switch(event.key) {
case Qt.Key_Control:
case Qt.Key_Shift:
case Qt.Key_Meta:
case Qt.Key_Alt:
break;
case Qt.Key_W:
if (window.closable && (event.modifiers === Qt.ControlModifier)) {
visible = false
event.accepted = true
}
// fall through
default:
// Consume unmodified keyboard entries while the window is focused, to prevent them
// from propagating to the application
if (event.modifiers === Qt.NoModifier) {
event.accepted = true;
}
break;
}
}
}

View file

@ -1,20 +1,48 @@
//
// DefaultFrame.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtGraphicalEffects 1.0
import "."
import "../controls"
import "../styles-uit"
Frame {
id: frame
property bool wideTopMargin: (window && (window.closable || window.title));
HifiConstants { id: hifi }
Rectangle {
anchors { margins: -iconSize; topMargin: -iconSize * (wideTopMargin ? 2 : 1); }
anchors.fill: parent;
color: "#7f7f7f7f";
radius: 3;
// Dialog frame
id: frameContent
// Allow dragging of the window
readonly property int iconSize: hifi.dimensions.frameIconSize
readonly property int frameMargin: 9
readonly property int frameMarginLeft: frameMargin
readonly property int frameMarginRight: frameMargin
readonly property int frameMarginTop: 2 * frameMargin + iconSize
readonly property int frameMarginBottom: iconSize + 11
anchors {
topMargin: -frameMarginTop
leftMargin: -frameMarginLeft
rightMargin: -frameMarginRight
bottomMargin: -frameMarginBottom
}
anchors.fill: parent
color: hifi.colors.baseGrayHighlight40
border {
width: hifi.dimensions.borderWidth
color: hifi.colors.faintGray50
}
radius: hifi.dimensions.borderRadius
// Enable dragging of the window
MouseArea {
anchors.fill: parent
drag.target: window
@ -22,48 +50,70 @@ Frame {
Row {
id: controlsRow
anchors { right: parent.right; top: parent.top; rightMargin: iconSize; topMargin: iconSize / 2; }
spacing: iconSize / 4
FontAwesome {
visible: false
text: "\uf08d"
style: Text.Outline; styleColor: "white"
size: frame.iconSize
rotation: !frame.parent ? 90 : frame.parent.pinned ? 0 : 90
color: frame.pinned ? "red" : "black"
anchors {
right: parent.right;
top: parent.top;
topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title
rightMargin: frameContent.frameMarginRight;
}
spacing: frameContent.iconSize / 4
HiFiGlyphs {
// "Pin" button
visible: window.pinnable
text: window.pinned ? hifi.glyphs.pinInverted : hifi.glyphs.pin
color: pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white
size: frameContent.iconSize
MouseArea {
id: pinClickArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
onClicked: { frame.pin(); mouse.accepted = false; }
onClicked: window.pinned = !window.pinned;
}
}
FontAwesome {
HiFiGlyphs {
// "Close" button
visible: window ? window.closable : false
text: closeClickArea.containsMouse ? "\uf057" : "\uf05c"
style: Text.Outline;
styleColor: "white"
color: closeClickArea.containsMouse ? "red" : "black"
size: frame.iconSize
text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close
color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white
size: frameContent.iconSize
MouseArea {
id: closeClickArea
anchors.fill: parent
hoverEnabled: true
onClicked: window.visible = false;
onClicked: window.shown = false;
}
}
}
Text {
RalewayRegular {
// Title
id: titleText
anchors { left: parent.left; leftMargin: iconSize; right: controlsRow.left; rightMargin: iconSize; top: parent.top; topMargin: iconSize / 2; }
anchors {
left: parent.left
leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x
right: controlsRow.left
rightMargin: frameContent.iconSize
top: parent.top
topMargin: frameContent.frameMargin
}
text: window ? window.title : ""
elide: Text.ElideRight
font.bold: true
color: (window && window.focus) ? "white" : "gray"
style: Text.Outline;
styleColor: "black"
color: hifi.colors.white
size: hifi.fontSizes.overlayTitle
}
DropShadow {
source: titleText
anchors.fill: titleText
horizontalOffset: 2
verticalOffset: 2
samples: 2
color: hifi.colors.baseGrayShadow60
visible: (window && window.focus)
cached: true
}
}
}

View file

@ -1,8 +1,18 @@
//
// Fadable.qml
//
// Created by Bradley Austin Davis on 15 Jan 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtGraphicalEffects 1.0
import "."
import "../styles"
import "../styles-uit"
// Enable window visibility transitions
FocusScope {
@ -13,6 +23,7 @@ FocusScope {
fadeTargetProperty = visible ? 1.0 : 0.0
}
property var completionCallback;
// The target property to animate, usually scale or opacity
property alias fadeTargetProperty: root.opacity
// always start the property at 0 to enable fade in on creation
@ -33,6 +44,13 @@ FocusScope {
fadeTargetProperty = target ? 1.0 : 0.0;
return;
}
// Now handle completions
if (completionCallback) {
completionCallback();
completionCallback = undefined;
}
}
// The actual animator
@ -43,8 +61,17 @@ FocusScope {
}
}
// Once we're transparent, disable the dialog's visibility
onFadeTargetPropertyChanged: {
visible = (fadeTargetProperty != 0.0);
}
function fadeIn(callback) {
completionCallback = callback;
fadeTargetProperty = 1.0;
}
function fadeOut(callback) {
completionCallback = callback;
fadeTargetProperty = 0.0;
}
}

View file

@ -1,24 +1,42 @@
import QtQuick 2.5
//
// Frame.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import "../controls"
import QtQuick 2.5
import QtGraphicalEffects 1.0
import "../styles-uit"
import "../js/Utils.js" as Utils
Item {
id: frame
HifiConstants { id: hifi }
default property var decoration
property bool gradientsSupported: desktop.gradientsSupported
readonly property int frameMarginLeft: frameContent.frameMarginLeft
readonly property int frameMarginRight: frameContent.frameMarginRight
readonly property int frameMarginTop: frameContent.frameMarginTop
readonly property int frameMarginBottom: frameContent.frameMarginBottom
// Frames always fill their parents, but their decorations may extend
// beyond the window via negative margin sizes
anchors.fill: parent
// Convenience accessor for the window
property alias window: frame.parent
readonly property int iconSize: 24
default property var decoration;
children: [
focusShadow,
decoration,
sizeOutline,
debugZ,
sizeDrag,
sizeDrag
]
Text {
@ -35,57 +53,81 @@ Item {
window.height = newSize.y
}
RadialGradient {
id: focusShadow
width: 1.66 * window.width
height: 1.66 * window.height
x: (window.width - width) / 2
y: window.height / 2 - 0.375 * height
visible: gradientsSupported && window && window.focus && window.content.visible
gradient: Gradient {
// GradientStop position 0.5 is at full circumference of circle that fits inside the square.
GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity
GradientStop { position: 0.333; color: "#1f000000" } // black, 12% opacity
GradientStop { position: 0.5; color: "#00000000" } // black, 0% opacity
GradientStop { position: 1.0; color: "#00000000" }
}
cached: true
}
Rectangle {
id: sizeOutline
width: window ? window.width : 0
height: window ? window.height : 0
color: "#00000000"
border.width: 4
radius: 10
x: -frameMarginLeft
y: -frameMarginTop
width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0
height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0
color: hifi.colors.baseGrayHighlight15
border.width: 3
border.color: hifi.colors.white50
radius: hifi.dimensions.borderRadius
visible: window ? !window.content.visible : false
}
MouseArea {
// Resize handle
id: sizeDrag
width: iconSize
height: iconSize
width: hifi.dimensions.frameIconSize
height: hifi.dimensions.frameIconSize
enabled: window ? window.resizable : false
x: window ? window.width : 0
y: window ? window.height : 0
hoverEnabled: true
x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0
y: window ? window.height + 4 : 0
property vector2d pressOrigin
property vector2d sizeOrigin
property bool hid: false
onPressed: {
console.log("Pressed on size")
//console.log("Pressed on size")
pressOrigin = Qt.vector2d(mouseX, mouseY)
sizeOrigin = Qt.vector2d(window.content.width, window.content.height)
hid = false;
}
onReleased: {
if (hid) {
window.content.visible = true
pane.visible = true
frameContent.visible = true
hid = false;
}
}
onPositionChanged: {
if (pressed) {
if (window.content.visible) {
window.content.visible = false;
if (pane.visible) {
pane.visible = false;
frameContent.visible = false
hid = true;
}
var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin);
frame.deltaSize(delta.x, delta.y)
}
}
FontAwesome {
HiFiGlyphs {
visible: sizeDrag.enabled
rotation: -45
anchors { centerIn: parent }
horizontalAlignment: Text.AlignHCenter
text: "\uf07d"
size: iconSize / 3 * 2
style: Text.Outline; styleColor: "white"
x: -11 // Move a little to visually align
y: window.modality == Qt.ApplicationModal ? -6 : -4
text: hifi.glyphs.resizeHandle
size: hifi.dimensions.frameIconSize + 10
color: sizeDrag.containsMouse || sizeDrag.pressed
? hifi.colors.white
: (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80)
}
}
}

View file

@ -2,7 +2,7 @@ import QtQuick 2.5
import "."
Frame {
Item {
id: frame
Item { anchors.fill: parent }

View file

@ -1,36 +1,98 @@
//
// ModalFrame.qml
//
// Created by Bradley Austin Davis on 15 Jan 2016
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import "."
import "../controls"
import "../controls-uit"
import "../styles-uit"
Frame {
id: frame
HifiConstants { id: hifi }
Item {
anchors.fill: parent
Rectangle {
id: frameContent
Rectangle {
id: background
anchors.fill: parent
anchors.margins: -4096
visible: window.visible
color: "#7f7f7f7f";
radius: 3;
readonly property bool hasTitle: window.title != ""
readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x
readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x
readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0)
readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y
signal frameClicked();
anchors {
fill: parent
topMargin: -frameMarginTop
leftMargin: -frameMarginLeft
rightMargin: -frameMarginRight
bottomMargin: -frameMarginBottom
}
Text {
y: -implicitHeight - iconSize / 2
text: window.title
elide: Text.ElideRight
font.bold: true
color: window.focus ? "white" : "gray"
style: Text.Outline;
styleColor: "black"
border {
width: hifi.dimensions.borderWidth
color: hifi.colors.lightGrayText80
}
radius: hifi.dimensions.borderRadius
color: hifi.colors.faintGray
// Enable dragging of the window
MouseArea {
anchors.fill: parent
drag.target: window
enabled: window.draggable
onClicked: window.frameClicked();
}
Item {
visible: frameContent.hasTitle
anchors.fill: parent
anchors {
topMargin: -parent.anchors.topMargin
leftMargin: -parent.anchors.leftMargin
rightMargin: -parent.anchors.rightMargin
}
Item {
width: title.width + (icon.text !== "" ? icon.width + hifi.dimensions.contentSpacing.x : 0)
x: (parent.width - width) / 2
onWidthChanged: window.titleWidth = width
HiFiGlyphs {
id: icon
text: window.iconText ? window.iconText : ""
size: window.iconSize ? window.iconSize : 30
color: hifi.colors.lightGray
visible: text != ""
anchors.verticalCenter: title.verticalCenter
anchors.left: parent.left
}
RalewayRegular {
id: title
text: window.title
elide: Text.ElideRight
color: hifi.colors.baseGrayHighlight
size: hifi.fontSizes.overlayTitle
y: -hifi.dimensions.modalDialogTitleHeight
anchors.right: parent.right
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: hifi.colors.lightGray
}
}
}
}

View file

@ -1,14 +1,28 @@
//
// ModalWindow.qml
//
// Created by Bradley Austin Davis on 22 Jan 2016
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import "."
Window {
id: root
anchors.centerIn: parent
ScrollingWindow {
id: window
modality: Qt.ApplicationModal
destroyOnCloseButton: true
destroyOnInvisible: true
frame: ModalFrame{}
destroyOnHidden: true
frame: ModalFrame { }
property int colorScheme: hifi.colorSchemes.light
property bool draggable: false
signal frameClicked();
anchors.centerIn: draggable ? undefined : parent
}

View file

@ -0,0 +1,157 @@
//
// Window.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
import "."
import "../styles-uit"
// FIXME how do I set the initial position of a window without
// overriding places where the a individual client of the window
// might be setting the position with a Settings{} element?
// FIXME how to I enable dragging without allowing the window to lay outside
// of the desktop? How do I ensure when the desktop resizes all the windows
// are still at least partially visible?
Window {
id: window
HifiConstants { id: hifi }
children: [ swallower, frame, pane, activator ]
property var footer: Item { } // Optional static footer at the bottom of the dialog.
// Scrollable window content.
// FIXME this should not define any visual content in this type. The base window
// type should only consist of logic sized areas, with nothing drawn (although the
// default value for the frame property does include visual decorations)
property var pane: Item {
property bool isScrolling: scrollView.height < scrollView.contentItem.height
property int contentWidth: scrollView.width - (isScrolling ? 10 : 0)
property int scrollHeight: scrollView.height
anchors.fill: parent
anchors.rightMargin: isScrolling ? 11 : 0
Rectangle {
id: contentBackground
anchors.fill: parent
anchors.rightMargin: parent.isScrolling ? 11 : 0
color: hifi.colors.baseGray
visible: !window.hideBackground && modality != Qt.ApplicationModal
}
LinearGradient {
visible: !window.hideBackground && gradientsSupported && modality != Qt.ApplicationModal
anchors.top: contentBackground.bottom
anchors.left: contentBackground.left
width: contentBackground.width - 1
height: 4
start: Qt.point(0, 0)
end: Qt.point(0, 4)
gradient: Gradient {
GradientStop { position: 0.0; color: hifi.colors.darkGray }
GradientStop { position: 1.0; color: hifi.colors.darkGray0 }
}
cached: true
}
ScrollView {
id: scrollView
contentItem: content
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
anchors.fill: parent
anchors.rightMargin: parent.isScrolling ? 1 : 0
anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0
style: ScrollViewStyle {
padding.right: -7 // Move to right away from content.
handle: Item {
implicitWidth: 8
Rectangle {
radius: 4
color: hifi.colors.white30
anchors {
fill: parent
leftMargin: 2 // Finesse size and position.
topMargin: 1
bottomMargin: 1
}
}
}
scrollBarBackground: Item {
implicitWidth: 10
Rectangle {
color: hifi.colors.darkGray30
radius: 4
anchors {
fill: parent
topMargin: -1 // Finesse size
bottomMargin: -2
}
}
}
incrementControl: Item {
visible: false
}
decrementControl: Item {
visible: false
}
}
}
Rectangle {
// Optional non-scrolling footer.
id: footerPane
anchors {
left: parent.left
bottom: parent.bottom
}
width: parent.contentWidth
height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3
color: hifi.colors.baseGray
visible: footer.height > 0
Item {
// Horizontal rule.
anchors.fill: parent
Rectangle {
width: parent.width
height: 1
y: 1 // Stop displaying content just above horizontal rule/=.
color: hifi.colors.baseGrayShadow
}
Rectangle {
width: parent.width
height: 1
y: 2
color: hifi.colors.baseGrayHighlight
}
}
Item {
anchors.fill: parent
anchors.topMargin: 3 // Horizontal rule.
children: [ footer ]
}
}
}
}

View file

@ -0,0 +1,96 @@
//
// DefaultFrame.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtGraphicalEffects 1.0
import "."
import "../styles-uit"
Frame {
HifiConstants { id: hifi }
property bool horizontalSpacers: false
property bool verticalSpacers: false
Rectangle {
// Dialog frame
id: frameContent
readonly property int frameMargin: 6
readonly property int frameMarginLeft: frameMargin + (horizontalSpacers ? 12 : 0)
readonly property int frameMarginRight: frameMargin + (horizontalSpacers ? 12 : 0)
readonly property int frameMarginTop: frameMargin + (verticalSpacers ? 12 : 0)
readonly property int frameMarginBottom: frameMargin + (verticalSpacers ? 12 : 0)
Rectangle {
visible: horizontalSpacers
anchors.left: parent.left
anchors.leftMargin: 6
anchors.verticalCenter: parent.verticalCenter
width: 8
height: window.height
color: "gray";
radius: 4
}
Rectangle {
visible: horizontalSpacers
anchors.right: parent.right
anchors.rightMargin: 6
anchors.verticalCenter: parent.verticalCenter
width: 8
height: window.height
color: "gray";
radius: 4
}
Rectangle {
visible: verticalSpacers
anchors.top: parent.top
anchors.topMargin: 6
anchors.horizontalCenter: parent.horizontalCenter
height: 8
width: window.width
color: "gray";
radius: 4
}
Rectangle {
visible: verticalSpacers
anchors.bottom: parent.bottom
anchors.bottomMargin: 6
anchors.horizontalCenter: parent.horizontalCenter
height: 8
width: window.width
color: "gray";
radius: 4
}
anchors {
leftMargin: -frameMarginLeft
rightMargin: -frameMarginRight
topMargin: -frameMarginTop
bottomMargin: -frameMarginBottom
}
anchors.fill: parent
color: hifi.colors.baseGrayHighlight40
border {
width: hifi.dimensions.borderWidth
color: hifi.colors.faintGray50
}
radius: hifi.dimensions.borderRadius / 2
// Enable dragging of the window
MouseArea {
anchors.fill: parent
drag.target: window
}
}
}

View file

@ -1,9 +1,20 @@
//
// Window.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
import "."
import "../styles"
import "../styles-uit"
// FIXME how do I set the initial position of a window without
// overriding places where the a individual client of the window
@ -15,16 +26,36 @@ import "../styles"
Fadable {
id: window
HifiConstants { id: hifi }
//
// Signals
//
signal windowDestroyed();
//
// Native properties
//
// The Window size is the size of the content, while the frame
// decorations can extend outside it.
implicitHeight: content ? content.height : 0
implicitWidth: content ? content.width : 0
x: desktop.invalid_position; y: desktop.invalid_position;
enabled: visible
children: [ swallower, frame, content, activator ]
signal windowDestroyed();
//
// Custom properties
//
property int modality: Qt.NonModal
// Corresponds to the window shown / hidden state AS DISTINCT from window visibility.
// Window visibility should NOT be used as a proxy for any other behavior.
property bool shown: true
// FIXME workaround to deal with the face that some visual items are defined here,
// when they should be moved to a frame derived type
property bool hideBackground: false
visible: shown
enabled: visible
readonly property bool topLevelWindow: true
property string title
// Should the window be closable control?
@ -34,17 +65,23 @@ Fadable {
// Should hitting the close button hide or destroy the window?
property bool destroyOnCloseButton: true
// Should hiding the window destroy it or just hide it?
property bool destroyOnInvisible: false
// FIXME support for pinned / unpinned pending full design
// property bool pinnable: false
// property bool pinned: false
property bool destroyOnHidden: false
property bool pinnable: true
property bool pinned: false
property bool resizable: false
property bool gradientsSupported: desktop.gradientsSupported
property int colorScheme: hifi.colorSchemes.dark
property vector2d minSize: Qt.vector2d(100, 100)
property vector2d maxSize: Qt.vector2d(1280, 720)
property vector2d maxSize: Qt.vector2d(1280, 800)
// The content to place inside the window, determined by the client
default property var content
property var footer: Item { } // Optional static footer at the bottom of the dialog.
function setDefaultFocus() {} // Default function; can be overridden by dialogs.
property var rectifier: Timer {
property bool executing: false;
interval: 100
@ -65,20 +102,15 @@ Fadable {
}
}
onXChanged: rectifier.begin();
onYChanged: rectifier.begin();
// This mouse area serves to raise the window. To function, it must live
// in the window and have a higher Z-order than the content, but follow
// the position and size of frame decoration
property var activator: MouseArea {
width: frame.decoration.width
height: frame.decoration.height
x: frame.decoration.anchors.margins
y: frame.decoration.anchors.topMargin
width: frame.decoration ? frame.decoration.width : window.width
height: frame.decoration ? frame.decoration.height : window.height
x: frame.decoration ? frame.decoration.anchors.leftMargin : 0
y: frame.decoration ? frame.decoration.anchors.topMargin : 0
propagateComposedEvents: true
hoverEnabled: true
acceptedButtons: Qt.AllButtons
enabled: window.visible
onPressed: {
@ -92,10 +124,10 @@ Fadable {
// to prevent things like mouse wheel events from reaching the application and changing
// the camera if the user is scrolling through a list and gets to the end.
property var swallower: MouseArea {
width: frame.decoration.width
height: frame.decoration.height
x: frame.decoration.anchors.margins
y: frame.decoration.anchors.topMargin
width: frame.decoration ? frame.decoration.width : window.width
height: frame.decoration ? frame.decoration.height : window.height
x: frame.decoration ? frame.decoration.anchors.leftMargin : 0
y: frame.decoration ? frame.decoration.anchors.topMargin : 0
hoverEnabled: true
acceptedButtons: Qt.AllButtons
enabled: window.visible
@ -106,71 +138,119 @@ Fadable {
onWheel: {}
}
// Default to a standard frame. Can be overriden to provide custom
// frame styles, like a full desktop frame to simulate a modal window
property var frame: DefaultFrame { }
property var frame: DefaultFrame {
//window: window
}
children: [ swallower, frame, content, activator ]
//
// Handlers
//
Component.onCompleted: {
window.parentChanged.connect(raise);
raise();
centerOrReposition();
setDefaultFocus();
d.centerOrReposition();
d.updateVisibility(shown);
}
Component.onDestruction: {
window.parentChanged.disconnect(raise); // Prevent warning on shutdown
windowDestroyed();
}
function centerOrReposition() {
if (x == desktop.invalid_position && y == desktop.invalid_position) {
desktop.centerOnVisible(window);
} else {
desktop.repositionOnVisible(window);
}
}
onXChanged: rectifier.begin();
onYChanged: rectifier.begin();
onShownChanged: d.updateVisibility(shown)
onVisibleChanged: {
if (!visible && destroyOnInvisible) {
destroy();
return;
}
if (visible) {
raise();
}
enabled = visible
if (visible && parent) {
centerOrReposition();
d.centerOrReposition();
}
}
QtObject {
id: d
readonly property alias pinned: window.pinned
readonly property alias shown: window.shown
readonly property alias modality: window.modality;
function getTargetVisibility() {
if (!window.shown) {
return false;
}
if (modality !== Qt.NonModal) {
return true;
}
if (pinned) {
return true;
}
if (desktop && !desktop.pinned) {
return true;
}
return false;
}
// The force flag causes all windows to fade back in, because a window was shown
readonly property alias visible: window.visible
function updateVisibility(force) {
if (force && !pinned && desktop.pinned) {
// Change the pinned state (which in turn will call us again)
desktop.pinned = false;
return;
}
var targetVisibility = getTargetVisibility();
if (targetVisibility === visible) {
return;
}
if (targetVisibility) {
fadeIn(function() {
if (force) {
window.raise();
}
});
} else {
fadeOut(function() {
if (!window.shown && window.destroyOnHidden) {
window.destroy();
}
});
}
}
function centerOrReposition() {
if (x == desktop.invalid_position && y == desktop.invalid_position) {
desktop.centerOnVisible(window);
} else {
desktop.repositionOnVisible(window);
}
}
}
// When the desktop pinned state changes, automatically handle the current windows
Connections { target: desktop; onPinnedChanged: d.updateVisibility() }
function raise() {
if (visible && parent) {
desktop.raise(window)
}
}
function pin() {
// pinned = ! pinned
function setPinned() {
pinned = !pinned
}
// our close function performs the same way as the OffscreenUI class:
// don't do anything but manipulate the targetVisible flag and let the other
// mechanisms decide if the window should be destroyed after the close
// animation completes
// FIXME using this close function messes up the visibility signals received by the
// type and it's derived types
// function close() {
// console.log("Closing " + window)
// if (destroyOnCloseButton) {
// destroyOnInvisible = true
// }
// visible = false;
// }
function framedRect() {
if (!frame || !frame.decoration) {
return Qt.rect(0, 0, window.width, window.height)
@ -180,7 +260,6 @@ Fadable {
window.height - frame.decoration.anchors.topMargin - frame.decoration.anchors.bottomMargin)
}
Keys.onPressed: {
switch(event.key) {
case Qt.Key_Control:
@ -189,10 +268,9 @@ Fadable {
case Qt.Key_Alt:
break;
case Qt.Key_W:
if (window.closable && (event.modifiers === Qt.ControlModifier)) {
visible = false
shown = false
event.accepted = true
}
// fall through

View file

@ -68,6 +68,7 @@
#include <input-plugins/InputPlugin.h>
#include <controllers/UserInputMapper.h>
#include <controllers/StateController.h>
#include <UserActivityLoggerScriptingInterface.h>
#include <LogHandler.h>
#include <MainWindow.h>
#include <MessagesClient.h>
@ -131,6 +132,7 @@
#include "scripting/WebWindowClass.h"
#include "scripting/WindowScriptingInterface.h"
#include "scripting/ControllerScriptingInterface.h"
#include "scripting/ToolbarScriptingInterface.h"
#include "scripting/RatesScriptingInterface.h"
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
#include "SpeechRecognizer.h"
@ -149,6 +151,8 @@
#include "InterfaceParentFinder.h"
#include "FrameTimingsScriptingInterface.h"
#include <GPUIdent.h>
#include <gl/GLHelpers.h>
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
// FIXME seems to be broken.
@ -435,7 +439,8 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<WindowScriptingInterface>();
DependencyManager::set<HMDScriptingInterface>();
DependencyManager::set<ResourceScriptingInterface>();
DependencyManager::set<ToolbarScriptingInterface>();
DependencyManager::set<UserActivityLoggerScriptingInterface>();
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
DependencyManager::set<SpeechRecognizer>();
@ -673,10 +678,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
accountManager->setIsAgent(true);
accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
// sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
// The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
UserActivityLogger::getInstance().launch(applicationVersion(), _previousSessionCrashed, sessionRunTime.get());
auto addressManager = DependencyManager::get<AddressManager>();
// use our MyAvatar position and quat for address manager path
@ -766,6 +767,39 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
// sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
// The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
auto gpuIdent = GPUIdent::getInstance();
auto glContextData = getGLContextData();
QJsonObject properties = {
{ "previousSessionCrashed", _previousSessionCrashed },
{ "previousSessionRuntime", sessionRunTime.get() },
{ "cpu_architecture", QSysInfo::currentCpuArchitecture() },
{ "kernel_type", QSysInfo::kernelType() },
{ "kernel_version", QSysInfo::kernelVersion() },
{ "os_type", QSysInfo::productType() },
{ "os_version", QSysInfo::productVersion() },
{ "gpu_name", gpuIdent->getName() },
{ "gpu_driver", gpuIdent->getDriver() },
{ "gpu_memory", static_cast<qint64>(gpuIdent->getMemory()) },
{ "gl_version_int", glVersionToInteger(glContextData.value("version").toString()) },
{ "gl_version", glContextData["version"] },
{ "gl_vender", glContextData["vendor"] },
{ "gl_sl_version", glContextData["slVersion"] },
{ "gl_renderer", glContextData["renderer"] }
};
auto macVersion = QSysInfo::macVersion();
if (macVersion != QSysInfo::MV_None) {
properties["os_osx_version"] = QSysInfo::macVersion();
}
auto windowsVersion = QSysInfo::windowsVersion();
if (windowsVersion != QSysInfo::WV_None) {
properties["os_win_version"] = QSysInfo::windowsVersion();
}
UserActivityLogger::getInstance().logAction("launch", properties);
// Tell our entity edit sender about our known jurisdictions
_entityEditSender.setServerJurisdictions(&_entityServerJurisdictions);
_entityEditSender.setMyAvatar(getMyAvatar());
@ -1061,6 +1095,89 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
}
});
// Add periodic checks to send user activity data
static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000;
static int SEND_STATS_INTERVAL_MS = 10000;
static int NEARBY_AVATAR_RADIUS_METERS = 10;
// Periodically send fps as a user activity event
QTimer* sendStatsTimer = new QTimer(this);
sendStatsTimer->setInterval(SEND_STATS_INTERVAL_MS);
connect(sendStatsTimer, &QTimer::timeout, this, [this]() {
QJsonObject properties = {};
MemoryInfo memInfo;
if (getMemoryInfo(memInfo)) {
properties["system_memory_total"] = static_cast<qint64>(memInfo.totalMemoryBytes);
properties["system_memory_used"] = static_cast<qint64>(memInfo.usedMemoryBytes);
properties["process_memory_used"] = static_cast<qint64>(memInfo.processUsedMemoryBytes);
}
auto displayPlugin = qApp->getActiveDisplayPlugin();
properties["fps"] = _frameCounter.rate();
properties["present_rate"] = displayPlugin->presentRate();
properties["new_frame_present_rate"] = displayPlugin->newFramePresentRate();
properties["dropped_frame_rate"] = displayPlugin->droppedFrameRate();
properties["sim_rate"] = getAverageSimsPerSecond();
properties["avatar_sim_rate"] = getAvatarSimrate();
auto bandwidthRecorder = DependencyManager::get<BandwidthRecorder>();
properties["packet_rate_in"] = bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond();
properties["packet_rate_out"] = bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond();
properties["kbps_in"] = bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond();
properties["kbps_out"] = bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond();
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer entityServerNode = nodeList->soloNodeOfType(NodeType::EntityServer);
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer);
SharedNodePointer messagesMixerNode = nodeList->soloNodeOfType(NodeType::MessagesMixer);
properties["entity_ping"] = entityServerNode ? entityServerNode->getPingMs() : -1;
properties["audio_ping"] = audioMixerNode ? audioMixerNode->getPingMs() : -1;
properties["avatar_ping"] = avatarMixerNode ? avatarMixerNode->getPingMs() : -1;
properties["asset_ping"] = assetServerNode ? assetServerNode->getPingMs() : -1;
properties["messages_ping"] = messagesMixerNode ? messagesMixerNode->getPingMs() : -1;
auto loadingRequests = ResourceCache::getLoadingRequests();
properties["active_downloads"] = loadingRequests.size();
properties["pending_downloads"] = ResourceCache::getPendingRequestCount();
properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false;
UserActivityLogger::getInstance().logAction("stats", properties);
});
sendStatsTimer->start();
// Periodically check for count of nearby avatars
static int lastCountOfNearbyAvatars = -1;
QTimer* checkNearbyAvatarsTimer = new QTimer(this);
checkNearbyAvatarsTimer->setInterval(CHECK_NEARBY_AVATARS_INTERVAL_MS);
connect(checkNearbyAvatarsTimer, &QTimer::timeout, this, [this]() {
auto avatarManager = DependencyManager::get<AvatarManager>();
int nearbyAvatars = avatarManager->numberOfAvatarsInRange(avatarManager->getMyAvatar()->getPosition(),
NEARBY_AVATAR_RADIUS_METERS) - 1;
if (nearbyAvatars != lastCountOfNearbyAvatars) {
lastCountOfNearbyAvatars = nearbyAvatars;
UserActivityLogger::getInstance().logAction("nearby_avatars", { { "count", nearbyAvatars } });
}
});
checkNearbyAvatarsTimer->start();
// Track user activity event when we receive a mute packet
auto onMutedByMixer = []() {
UserActivityLogger::getInstance().logAction("received_mute_packet");
};
connect(DependencyManager::get<AudioClient>().data(), &AudioClient::mutedByMixer, this, onMutedByMixer);
// Track when the address bar is opened
auto onAddressBarToggled = [this]() {
// Record time
UserActivityLogger::getInstance().logAction("opened_address_bar", { { "uptime_ms", _sessionRunTimer.elapsed() } });
};
connect(DependencyManager::get<DialogsManager>().data(), &DialogsManager::addressBarToggled, this, onAddressBarToggled);
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
@ -1419,7 +1536,7 @@ void Application::initializeUi() {
rootContext->setContextProperty("Overlays", &_overlays);
rootContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
rootContext->setContextProperty("Menu", MenuScriptingInterface::getInstance());
rootContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
rootContext->setContextProperty("Stats", Stats::getInstance());
rootContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
rootContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
@ -2070,7 +2187,8 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_X:
if (isShifted && isMeta) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getRootContext()->engine()->clearComponentCache();
offscreenUi->togglePinned();
//offscreenUi->getRootContext()->engine()->clearComponentCache();
//OffscreenUi::information("Debugging", "Component cache cleared");
// placeholder for dialogs being converted to QML.
}
@ -2719,7 +2837,6 @@ void Application::idle(float nsecsElapsed) {
if (firstIdle) {
firstIdle = false;
connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
_overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays));
}
PROFILE_RANGE(__FUNCTION__);
@ -3219,13 +3336,13 @@ void Application::updateThreads(float deltaTime) {
}
void Application::toggleOverlays() {
auto newOverlaysVisible = !_overlayConductor.getEnabled();
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, newOverlaysVisible);
_overlayConductor.setEnabled(newOverlaysVisible);
auto menu = Menu::getInstance();
menu->setIsOptionChecked(MenuOption::Overlays, menu->isOptionChecked(MenuOption::Overlays));
}
void Application::setOverlaysVisible(bool visible) {
_overlayConductor.setEnabled(visible);
auto menu = Menu::getInstance();
menu->setIsOptionChecked(MenuOption::Overlays, true);
}
void Application::cycleCamera() {
@ -4210,6 +4327,7 @@ void Application::resetSensors(bool andReload) {
DependencyManager::get<DdeFaceTracker>()->reset();
DependencyManager::get<EyeTracker>()->reset();
getActiveDisplayPlugin()->resetSensors();
_overlayConductor.centerUI();
getMyAvatar()->reset(andReload);
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection);
}
@ -4526,6 +4644,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
RayToOverlayIntersectionResultFromScriptValue);
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data());
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
@ -4574,6 +4693,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface());
scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
}
bool Application::canAcceptURL(const QString& urlString) const {
@ -5176,6 +5297,11 @@ void Application::updateDisplayMode() {
return;
}
UserActivityLogger::getInstance().logAction("changed_display_mode", {
{ "previous_display_mode", _displayPlugin ? _displayPlugin->getName() : "" },
{ "display_mode", newDisplayPlugin ? newDisplayPlugin->getName() : "" }
});
auto offscreenUi = DependencyManager::get<OffscreenUi>();
// Make the switch atomic from the perspective of other threads
@ -5300,9 +5426,7 @@ void Application::readArgumentsFromLocalSocket() const {
}
void Application::showDesktop() {
if (!_overlayConductor.getEnabled()) {
_overlayConductor.setEnabled(true);
}
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, true);
}
CompositorHelper& Application::getApplicationCompositor() const {

View file

@ -211,6 +211,8 @@ public:
float getRenderResolutionScale() const;
qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); }
bool isAboutToQuit() const { return _aboutToQuit; }
// the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display

View file

@ -80,7 +80,8 @@ void DiscoverabilityManager::updateLocation() {
locationObject.insert(FRIENDS_ONLY_KEY_IN_LOCATION, (_mode.get() == Discoverability::Friends));
// if we have a session ID add it now, otherwise add a null value
rootObject[SESSION_ID_KEY] = _sessionID.isEmpty() ? QJsonValue() : _sessionID;
auto sessionID = accountManager->getSessionID();
rootObject[SESSION_ID_KEY] = sessionID.isNull() ? QJsonValue() : sessionID.toString();
JSONCallbackParameters callbackParameters;
callbackParameters.jsonCallbackReceiver = this;
@ -110,11 +111,8 @@ void DiscoverabilityManager::updateLocation() {
callbackParameters.jsonCallbackMethod = "handleHeartbeatResponse";
QJsonObject heartbeatObject;
if (!_sessionID.isEmpty()) {
heartbeatObject[SESSION_ID_KEY] = _sessionID;
} else {
heartbeatObject[SESSION_ID_KEY] = QJsonValue();
}
auto sessionID = accountManager->getSessionID();
heartbeatObject[SESSION_ID_KEY] = sessionID.isNull() ? QJsonValue() : sessionID.toString();
accountManager->sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional,
QNetworkAccessManager::PutOperation, callbackParameters,
@ -126,11 +124,11 @@ void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply
auto dataObject = AccountManager::dataObjectFromResponse(requestReply);
if (!dataObject.isEmpty()) {
_sessionID = dataObject[SESSION_ID_KEY].toString();
auto sessionID = dataObject[SESSION_ID_KEY].toString();
// give that session ID to the account manager
auto accountManager = DependencyManager::get<AccountManager>();
accountManager->setSessionID(_sessionID);
accountManager->setSessionID(sessionID);
}
}

View file

@ -49,7 +49,6 @@ private:
DiscoverabilityManager();
Setting::Handle<int> _mode;
QString _sessionID;
QJsonObject _lastLocationObject;
};

View file

@ -256,8 +256,7 @@ Menu::Menu() {
UNSPECIFIED_POSITION, "Advanced");
// View > Overlays
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true,
qApp, SLOT(setOverlaysVisible(bool)));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true);
// Navigate menu ----------------------------------
MenuWrapper* navigateMenu = addMenu("Navigate");

View file

@ -722,7 +722,7 @@ void MyAvatar::saveData() {
settings.setValue("displayName", _displayName);
settings.setValue("collisionSoundURL", _collisionSoundURL);
settings.setValue("useSnapTurn", _useSnapTurn);
settings.setValue("clearOverlayWhenDriving", _clearOverlayWhenDriving);
settings.setValue("clearOverlayWhenMoving", _clearOverlayWhenMoving);
settings.endGroup();
}
@ -842,7 +842,7 @@ void MyAvatar::loadData() {
setDisplayName(settings.value("displayName").toString());
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());
setClearOverlayWhenDriving(settings.value("clearOverlayWhenDriving", _clearOverlayWhenDriving).toBool());
setClearOverlayWhenMoving(settings.value("clearOverlayWhenMoving", _clearOverlayWhenMoving).toBool());
settings.endGroup();
@ -1248,8 +1248,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
_characterController.setPositionAndOrientation(getPosition(), getOrientation());
if (qApp->isHMDMode()) {
bool hasDriveInput = fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f;
_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput);
_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput());
} else {
_follow.deactivate();
}
@ -2134,3 +2133,7 @@ bool MyAvatar::didTeleport() {
lastPosition = pos;
return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME);
}
bool MyAvatar::hasDriveInput() const {
return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f;
}

View file

@ -159,8 +159,8 @@ public:
Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; }
Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; }
Q_INVOKABLE bool getClearOverlayWhenDriving() const { return _clearOverlayWhenDriving; }
Q_INVOKABLE void setClearOverlayWhenDriving(bool on) { _clearOverlayWhenDriving = on; }
Q_INVOKABLE bool getClearOverlayWhenMoving() const { return _clearOverlayWhenMoving; }
Q_INVOKABLE void setClearOverlayWhenMoving(bool on) { _clearOverlayWhenMoving = on; }
Q_INVOKABLE void setHMDLeanRecenterEnabled(bool value) { _hmdLeanRecenterEnabled = value; }
Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; }
@ -266,6 +266,8 @@ public:
controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
controller::Pose getRightHandControllerPoseInAvatarFrame() const;
bool hasDriveInput() const;
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled);
Q_INVOKABLE bool getCharacterControllerEnabled();
@ -403,7 +405,7 @@ private:
QString _fullAvatarModelName;
QUrl _animGraphUrl {""};
bool _useSnapTurn { true };
bool _clearOverlayWhenDriving { false };
bool _clearOverlayWhenMoving { true };
// cache of the current HMD sensor position and orientation
// in sensor space.

View file

@ -16,6 +16,7 @@
#include "Application.h"
#include "MainWindow.h"
#include <display-plugins/CompositorHelper.h>
int DesktopScriptingInterface::getWidth() {
QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize();
@ -25,3 +26,8 @@ int DesktopScriptingInterface::getHeight() {
QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize();
return size.height();
}
void DesktopScriptingInterface::setOverlayAlpha(float alpha) {
qApp->getApplicationCompositor().setAlpha(alpha);
}

View file

@ -22,6 +22,8 @@ class DesktopScriptingInterface : public QObject, public Dependency {
Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus
public:
Q_INVOKABLE void setOverlayAlpha(float alpha);
int getWidth();
int getHeight();
};

View file

@ -105,3 +105,25 @@ QString HMDScriptingInterface::preferredAudioInput() const {
QString HMDScriptingInterface::preferredAudioOutput() const {
return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice();
}
bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const {
return qApp->getActiveDisplayPlugin()->setHandLaser(hands,
enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None,
color, direction);
}
void HMDScriptingInterface::disableHandLasers(int hands) const {
qApp->getActiveDisplayPlugin()->setHandLaser(hands, DisplayPlugin::HandLaserMode::None);
}
bool HMDScriptingInterface::suppressKeyboard() {
return qApp->getActiveDisplayPlugin()->suppressKeyboard();
}
void HMDScriptingInterface::unsuppressKeyboard() {
qApp->getActiveDisplayPlugin()->unsuppressKeyboard();
}
bool HMDScriptingInterface::isKeyboardVisible() {
return qApp->getActiveDisplayPlugin()->isKeyboardVisible();
}

View file

@ -1,4 +1,4 @@
//
// HMDScriptingInterface.h
// interface/src/scripting
//
@ -12,6 +12,8 @@
#ifndef hifi_HMDScriptingInterface_h
#define hifi_HMDScriptingInterface_h
#include <atomic>
#include <QtScript/QScriptValue>
class QScriptContext;
class QScriptEngine;
@ -31,12 +33,28 @@ public:
Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const;
Q_INVOKABLE glm::vec2 overlayFromWorldPoint(const glm::vec3& position) const;
Q_INVOKABLE glm::vec3 worldPointFromOverlay(const glm::vec2& overlay) const;
Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const;
Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const;
Q_INVOKABLE QString preferredAudioInput() const;
Q_INVOKABLE QString preferredAudioOutput() const;
Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const;
Q_INVOKABLE void disableHandLasers(int hands) const;
/// Suppress the activation of any on-screen keyboard so that a script operation will
/// not be interrupted by a keyboard popup
/// Returns false if there is already an active keyboard displayed.
/// Clients should re-enable the keyboard when the operation is complete and ensure
/// that they balance any call to suppressKeyboard() that returns true with a corresponding
/// call to unsuppressKeyboard() within a reasonable amount of time
Q_INVOKABLE bool suppressKeyboard();
/// Enable the keyboard following a suppressKeyboard call
Q_INVOKABLE void unsuppressKeyboard();
/// Query the display plugin to determine the current VR keyboard visibility
Q_INVOKABLE bool isKeyboardVisible();
public:
HMDScriptingInterface();
static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine);

View file

@ -0,0 +1,115 @@
//
// Created by Bradley Austin Davis on 2016-06-16
// Copyright 2013-2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ToolbarScriptingInterface.h"
#include <OffscreenUi.h>
class QmlWrapper : public QObject {
Q_OBJECT
public:
QmlWrapper(QObject* qmlObject, QObject* parent = nullptr)
: QObject(parent), _qmlObject(qmlObject) {
}
Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->executeOnUiThread([=] {
_qmlObject->setProperty(propertyName.toStdString().c_str(), propertyValue);
});
}
Q_INVOKABLE void writeProperties(QVariant propertyMap) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->executeOnUiThread([=] {
QVariantMap map = propertyMap.toMap();
for (const QString& key : map.keys()) {
_qmlObject->setProperty(key.toStdString().c_str(), map[key]);
}
});
}
Q_INVOKABLE QVariant readProperty(const QString& propertyName) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
return offscreenUi->returnFromUiThread([&]()->QVariant {
return _qmlObject->property(propertyName.toStdString().c_str());
});
}
Q_INVOKABLE QVariant readProperties(const QVariant& propertyList) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
return offscreenUi->returnFromUiThread([&]()->QVariant {
QVariantMap result;
for (const QVariant& property : propertyList.toList()) {
QString propertyString = property.toString();
result.insert(propertyString, _qmlObject->property(propertyString.toStdString().c_str()));
}
return result;
});
}
protected:
QObject* _qmlObject{ nullptr };
};
class ToolbarButtonProxy : public QmlWrapper {
Q_OBJECT
public:
ToolbarButtonProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) {
connect(qmlObject, SIGNAL(clicked()), this, SIGNAL(clicked()));
}
signals:
void clicked();
};
class ToolbarProxy : public QmlWrapper {
Q_OBJECT
public:
ToolbarProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) { }
Q_INVOKABLE QObject* addButton(const QVariant& properties) {
QVariant resultVar;
bool invokeResult = QMetaObject::invokeMethod(_qmlObject, "addButton", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, properties));
if (!invokeResult) {
return nullptr;
}
QObject* rawButton = qvariant_cast<QObject *>(resultVar);
if (!rawButton) {
return nullptr;
}
return new ToolbarButtonProxy(rawButton, this);
}
};
QObject* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto desktop = offscreenUi->getDesktop();
QVariant resultVar;
bool invokeResult = QMetaObject::invokeMethod(desktop, "getToolbar", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, toolbarId));
if (!invokeResult) {
return nullptr;
}
QObject* rawToolbar = qvariant_cast<QObject *>(resultVar);
if (!rawToolbar) {
return nullptr;
}
return new ToolbarProxy(rawToolbar);
}
#include "ToolbarScriptingInterface.moc"

View file

@ -0,0 +1,26 @@
//
// Created by Bradley Austin Davis on 2016-06-16
// Copyright 2013-2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ToolbarScriptingInterface_h
#define hifi_ToolbarScriptingInterface_h
#include <mutex>
#include <QtCore/QObject>
#include <DependencyManager.h>
class ToolbarProxy;
class ToolbarScriptingInterface : public QObject, public Dependency {
Q_OBJECT
public:
Q_INVOKABLE QObject* getToolbar(const QString& toolbarId);
};
#endif // hifi_ToolbarScriptingInterface_h

View file

@ -17,179 +17,134 @@
#include "OverlayConductor.h"
OverlayConductor::OverlayConductor() {
}
OverlayConductor::~OverlayConductor() {
}
void OverlayConductor::update(float dt) {
bool OverlayConductor::headOutsideOverlay() const {
glm::mat4 hmdMat = qApp->getHMDSensorPose();
glm::vec3 hmdPos = extractTranslation(hmdMat);
glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f));
updateMode();
Transform uiTransform = qApp->getApplicationCompositor().getModelTransform();
glm::vec3 uiPos = uiTransform.getTranslation();
glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
switch (_mode) {
case SITTING: {
// when sitting, the overlay is at the origin, facing down the -z axis.
// the camera is taken directly from the HMD.
Transform identity;
qApp->getApplicationCompositor().setModelTransform(identity);
qApp->getApplicationCompositor().setCameraBaseTransform(identity);
break;
}
case STANDING: {
// when standing, the overlay is at a reference position, which is set when the overlay is
// enabled. The camera is taken directly from the HMD, but in world space.
// So the sensorToWorldMatrix must be applied.
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
Transform t;
t.evalFromRawMatrix(myAvatar->getSensorToWorldMatrix());
qApp->getApplicationCompositor().setCameraBaseTransform(t);
// detect when head moves out side of sweet spot, or looks away.
mat4 headMat = myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose();
vec3 headWorldPos = extractTranslation(headMat);
vec3 headForward = glm::quat_cast(headMat) * glm::vec3(0.0f, 0.0f, -1.0f);
Transform modelXform = qApp->getApplicationCompositor().getModelTransform();
vec3 compositorWorldPos = modelXform.getTranslation();
vec3 compositorForward = modelXform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
const float MAX_COMPOSITOR_DISTANCE = 0.6f;
const float MAX_COMPOSITOR_ANGLE = 110.0f;
if (_enabled && (glm::distance(headWorldPos, compositorWorldPos) > MAX_COMPOSITOR_DISTANCE ||
glm::dot(headForward, compositorForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE)))) {
// fade out the overlay
setEnabled(false);
}
break;
}
case FLAT:
// do nothing
break;
const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface.
const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled
if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE ||
glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) {
return true;
}
return false;
}
void OverlayConductor::updateMode() {
bool OverlayConductor::updateAvatarIsAtRest() {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
if (myAvatar->getClearOverlayWhenDriving()) {
float speed = glm::length(myAvatar->getVelocity());
const float MIN_DRIVING = 0.2f;
const float MAX_NOT_DRIVING = 0.01f;
const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000;
const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000;
bool nowDriving = _driving; // Assume current _driving mode unless...
if (speed > MIN_DRIVING) { // ... we're definitely moving...
nowDriving = true;
} else if (speed < MAX_NOT_DRIVING) { // ... or definitely not.
nowDriving = false;
}
// Check that we're in this new mode for long enough to really trigger a transition.
if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer.
_timeInPotentialMode = 0;
} else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now.
_timeInPotentialMode = usecTimestampNow();
} else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) {
_timeInPotentialMode = 0; // a real transition
if (nowDriving) {
_wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays);
} else { // reset when coming out of driving
_mode = FLAT; // Seems appropriate to let things reset, below, after the following.
// All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments.
qApp->getActiveDisplayPlugin()->resetSensors();
myAvatar->reset(true, false, false);
}
if (_wantsOverlays) {
setEnabled(!nowDriving, false);
}
_driving = nowDriving;
} // Else haven't accumulated enough time in new mode, but keep timing.
const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s
const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms
const float AT_REST_THRESHOLD = 0.01f;
bool desiredAtRest = glm::length(myAvatar->getVelocity()) < AT_REST_THRESHOLD;
if (desiredAtRest != _desiredAtRest) {
// start timer
_desiredAtRestTimer = usecTimestampNow() + (desiredAtRest ? REST_ENABLE_TIME_USECS : REST_DISABLE_TIME_USECS);
}
Mode newMode;
if (qApp->isHMDMode()) {
newMode = SITTING;
_desiredAtRest = desiredAtRest;
if (_desiredAtRestTimer != 0 && usecTimestampNow() > _desiredAtRestTimer) {
// timer expired
// change state!
_currentAtRest = _desiredAtRest;
// disable timer
_desiredAtRestTimer = 0;
}
return _currentAtRest;
}
bool OverlayConductor::updateAvatarHasDriveInput() {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms
const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s
bool desiredDriving = myAvatar->hasDriveInput();
if (desiredDriving != _desiredDriving) {
// start timer
_desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS);
}
_desiredDriving = desiredDriving;
if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) {
// timer expired
// change state!
_currentDriving = _desiredDriving;
// disable timer
_desiredDrivingTimer = 0;
}
return _currentDriving;
}
void OverlayConductor::centerUI() {
// place the overlay at the current hmd position in sensor space
auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose());
qApp->getApplicationCompositor().setModelTransform(Transform(camMat));
}
void OverlayConductor::update(float dt) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
bool currentVisible = !offscreenUi->getDesktop()->property("pinned").toBool();
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
// centerUI when hmd mode is first enabled and mounted
if (qApp->isHMDMode() && qApp->getActiveDisplayPlugin()->isDisplayVisible()) {
if (!_hmdMode) {
_hmdMode = true;
centerUI();
}
} else {
newMode = FLAT;
_hmdMode = false;
}
if (newMode != _mode) {
switch (newMode) {
case SITTING: {
// enter the SITTING state
// place the overlay at origin
Transform identity;
qApp->getApplicationCompositor().setModelTransform(identity);
break;
}
case STANDING: { // STANDING mode is not currently used.
// enter the STANDING state
// place the overlay at the current hmd position in world space
auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose());
Transform t;
t.setTranslation(extractTranslation(camMat));
t.setRotation(glm::quat_cast(camMat));
qApp->getApplicationCompositor().setModelTransform(t);
break;
}
bool prevDriving = _currentDriving;
bool isDriving = updateAvatarHasDriveInput();
bool drivingChanged = prevDriving != isDriving;
bool isAtRest = updateAvatarIsAtRest();
case FLAT:
// do nothing
break;
if (_flags & SuppressedByDrive) {
if (!isDriving) {
_flags &= ~SuppressedByDrive;
}
} else {
if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) {
_flags |= SuppressedByDrive;
}
}
_mode = newMode;
}
void OverlayConductor::setEnabled(bool enabled, bool toggleQmlEvents) {
if (enabled == _enabled) {
return;
if (_flags & SuppressedByHead) {
if (isAtRest) {
_flags &= ~SuppressedByHead;
}
} else {
if (_hmdMode && headOutsideOverlay()) {
_flags |= SuppressedByHead;
}
}
if (toggleQmlEvents) { // Could recurse on us with the wrong toggleQmlEvents flag, and not need in the !toggleQmlEvent case anyway.
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, enabled);
}
_enabled = enabled; // set the new value
// if the new state is visible/enabled...
if (_enabled) {
// alpha fadeIn the overlay mesh.
qApp->getApplicationCompositor().fadeIn();
// enable mouse clicks from script
qApp->getOverlays().enable();
// enable QML events
if (toggleQmlEvents) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getRootItem()->setEnabled(true);
}
if (_mode == STANDING) {
// place the overlay at the current hmd position in world space
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose());
Transform t;
t.setTranslation(extractTranslation(camMat));
t.setRotation(glm::quat_cast(camMat));
qApp->getApplicationCompositor().setModelTransform(t);
}
} else { // other wise, if the new state is hidden/not enabled
// alpha fadeOut the overlay mesh.
qApp->getApplicationCompositor().fadeOut();
// disable mouse clicks from script
qApp->getOverlays().disable();
// disable QML events
if (toggleQmlEvents) { // I'd really rather always do this, but it looses drive state. bugzid:501
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getRootItem()->setEnabled(false);
bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask));
if (targetVisible != currentVisible) {
offscreenUi->setPinned(!targetVisible);
if (targetVisible && _hmdMode) {
centerUI();
}
}
}
bool OverlayConductor::getEnabled() const {
return _enabled;
}

View file

@ -17,23 +17,31 @@ public:
~OverlayConductor();
void update(float dt);
void setEnabled(bool enable, bool toggleQmlEvents = true);
bool getEnabled() const;
void centerUI();
private:
void updateMode();
bool headOutsideOverlay() const;
bool updateAvatarHasDriveInput();
bool updateAvatarIsAtRest();
enum Mode {
FLAT,
SITTING,
STANDING
enum SupressionFlags {
SuppressedByDrive = 0x01,
SuppressedByHead = 0x02,
SuppressMask = 0x03,
};
Mode _mode { FLAT };
bool _enabled { false };
bool _driving { false };
quint64 _timeInPotentialMode { 0 };
bool _wantsOverlays { true };
uint8_t _flags { SuppressedByDrive };
bool _hmdMode { false };
// used by updateAvatarHasDriveInput
quint64 _desiredDrivingTimer { 0 };
bool _desiredDriving { false };
bool _currentDriving { false };
// used by updateAvatarIsAtRest
quint64 _desiredAtRestTimer { 0 };
bool _desiredAtRest { true };
bool _currentAtRest { true };
};
#endif

View file

@ -62,9 +62,9 @@ void setupPreferences() {
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Snap turn when in HMD", getter, setter));
}
{
auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenDriving(); };
auto setter = [=](bool value) { myAvatar->setClearOverlayWhenDriving(value); };
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when driving", getter, setter));
auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenMoving(); };
auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); };
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter));
}
{
auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); };

View file

@ -44,6 +44,20 @@ bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range
return false;
}
int AvatarHashMap::numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters) {
auto hashCopy = getHashCopy();
auto rangeMeters2 = rangeMeters * rangeMeters;
int count = 0;
for (const AvatarSharedPointer& sharedAvatar : hashCopy) {
glm::vec3 avatarPosition = sharedAvatar->getPosition();
auto distance2 = glm::distance2(avatarPosition, position);
if (distance2 < rangeMeters2) {
++count;
}
}
return count;
}
AvatarSharedPointer AvatarHashMap::newSharedAvatar() {
return std::make_shared<AvatarData>();
}

View file

@ -39,6 +39,7 @@ public:
Q_INVOKABLE AvatarData* getAvatar(QUuid avatarID);
virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) { return findAvatar(sessionID); }
int numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters);
signals:
void avatarAddedEvent(const QUuid& sessionUUID);

View file

@ -32,6 +32,7 @@ class Mapping;
using MappingPointer = std::shared_ptr<Mapping>;
using MappingList = std::list<MappingPointer>;
struct Pose;
}
#endif

View file

@ -44,7 +44,8 @@ namespace controller {
LS_TOUCH,
LEFT_THUMB_UP,
LS_CENTER,
LS_OUTER,
LS_X,
LS_Y,
RIGHT_PRIMARY_THUMB,
RIGHT_SECONDARY_THUMB,
@ -53,7 +54,8 @@ namespace controller {
RS_TOUCH,
RIGHT_THUMB_UP,
RS_CENTER,
RS_OUTER,
RS_X,
RS_Y,
LEFT_PRIMARY_INDEX,
LEFT_SECONDARY_INDEX,

View file

@ -336,7 +336,9 @@ void CompositorHelper::computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3&
}
glm::mat4 CompositorHelper::getUiTransform() const {
return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose());
glm::mat4 modelMat;
_modelTransform.getMatrix(modelMat);
return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()) * modelMat;
}
//Finds the collision point of a world space ray
@ -346,7 +348,7 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c
auto relativePosition = vec3(relativePosition4) / relativePosition4.w;
auto relativeDirection = glm::inverse(glm::quat_cast(UITransform)) * direction;
float uiRadius = _oculusUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale
float uiRadius = _hmdUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale
float instersectionDistance;
if (raySphereIntersect(relativeDirection, relativePosition, uiRadius, &instersectionDistance)){
@ -407,84 +409,25 @@ void CompositorHelper::updateTooltips() {
//}
}
static const float FADE_DURATION = 500.0f;
static const float FADE_IN_ALPHA = 1.0f;
static const float FADE_OUT_ALPHA = 0.0f;
void CompositorHelper::startFadeFailsafe(float endValue) {
_fadeStarted = usecTimestampNow();
_fadeFailsafeEndValue = endValue;
const int SLIGHT_DELAY = 10;
QTimer::singleShot(FADE_DURATION + SLIGHT_DELAY, [this]{
checkFadeFailsafe();
});
}
void CompositorHelper::checkFadeFailsafe() {
auto elapsedInFade = usecTimestampNow() - _fadeStarted;
if (elapsedInFade > FADE_DURATION) {
setAlpha(_fadeFailsafeEndValue);
}
}
void CompositorHelper::fadeIn() {
_fadeInAlpha = true;
_alphaPropertyAnimation->setDuration(FADE_DURATION);
_alphaPropertyAnimation->setStartValue(_alpha);
_alphaPropertyAnimation->setEndValue(FADE_IN_ALPHA);
_alphaPropertyAnimation->start();
// Sometimes, this "QPropertyAnimation" fails to complete the animation, and we end up with a partially faded
// state. So we will also have this fail-safe, where we record the timestamp of the fadeRequest, and the target
// value of the fade, and if after that time we still haven't faded all the way, we will kick it to the final
// fade value
startFadeFailsafe(FADE_IN_ALPHA);
}
void CompositorHelper::fadeOut() {
_fadeInAlpha = false;
_alphaPropertyAnimation->setDuration(FADE_DURATION);
_alphaPropertyAnimation->setStartValue(_alpha);
_alphaPropertyAnimation->setEndValue(FADE_OUT_ALPHA);
_alphaPropertyAnimation->start();
startFadeFailsafe(FADE_OUT_ALPHA);
}
void CompositorHelper::toggle() {
if (_fadeInAlpha) {
fadeOut();
} else {
fadeIn();
}
}
// eyePose and headPosition are in sensor space.
// the resulting matrix should be in view space.
glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const glm::vec3& headPosition) const {
glm::mat4 result;
if (isHMD()) {
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize);
auto reticlePosition = getReticlePosition();
auto spherical = overlayToSpherical(reticlePosition);
// The pointer transform relative to the sensor
auto pointerTransform = glm::mat4_cast(quat(vec3(-spherical.y, spherical.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
float reticleDepth = getReticleDepth();
if (reticleDepth != 1.0f) {
// Cursor position in UI space
auto cursorPosition = vec3(pointerTransform[3]) / pointerTransform[3].w;
// Ray to the cursor, in UI space
auto cursorRay = glm::normalize(cursorPosition - headPosition) * reticleDepth;
// Move the ray to be relative to the head pose
pointerTransform[3] = vec4(cursorRay + headPosition, 1);
// Scale up the cursor because of distance
reticleScale *= reticleDepth;
vec2 spherical = overlayToSpherical(getReticlePosition());
vec3 overlaySurfacePoint = getPoint(spherical.x, spherical.y); // overlay space
vec3 sensorSurfacePoint = _modelTransform.transform(overlaySurfacePoint); // sensor space
vec3 d = sensorSurfacePoint - headPosition;
vec3 reticlePosition;
if (glm::length(d) >= EPSILON) {
d = glm::normalize(d);
} else {
d = glm::normalize(overlaySurfacePoint);
}
glm::mat4 overlayXfm;
_modelTransform.getMatrix(overlayXfm);
pointerTransform = overlayXfm * pointerTransform;
pointerTransform = glm::inverse(eyePose) * pointerTransform;
result = glm::scale(pointerTransform, reticleScale);
reticlePosition = headPosition + (d * getReticleDepth());
quat reticleOrientation = glm::quat_cast(_currentDisplayPlugin->getHeadPose());
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * getReticleDepth());
return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition);
} else {
static const float CURSOR_PIXEL_SIZE = 32.0f;
const auto canvasSize = vec2(toGlm(_renderingWidget->size()));;

View file

@ -38,7 +38,7 @@ const float MAGNIFY_MULT = 2.0f;
class CompositorHelper : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha)
Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha NOTIFY alphaChanged)
Q_PROPERTY(bool reticleOverDesktop READ getReticleOverDesktop WRITE setReticleOverDesktop)
public:
static const uvec2 VIRTUAL_SCREEN_SIZE;
@ -75,12 +75,8 @@ public:
void setModelTransform(const Transform& transform) { _modelTransform = transform; }
const Transform& getModelTransform() const { return _modelTransform; }
void fadeIn();
void fadeOut();
void toggle();
float getAlpha() const { return _alpha; }
void setAlpha(float alpha) { _alpha = alpha; }
void setAlpha(float alpha) { if (alpha != _alpha) { emit alphaChanged(); _alpha = alpha; } }
bool getReticleVisible() const { return _reticleVisible; }
void setReticleVisible(bool visible) { _reticleVisible = visible; }
@ -113,10 +109,11 @@ public:
void setReticleOverDesktop(bool value) { _isOverDesktop = value; }
void setDisplayPlugin(const DisplayPluginPointer& displayPlugin) { _currentDisplayPlugin = displayPlugin; }
void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; _currentFrame = frame; }
void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; }
signals:
void allowMouseCaptureChanged();
void alphaChanged();
protected slots:
void sendFakeMouseEvent();
@ -127,7 +124,6 @@ private:
DisplayPluginPointer _currentDisplayPlugin;
glm::mat4 _currentCamera;
uint32_t _currentFrame { 0 };
QWidget* _renderingWidget{ nullptr };
//// Support for hovering and tooltips
@ -144,16 +140,7 @@ private:
float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO };
float _alpha { 1.0f };
float _prevAlpha { 1.0f };
float _fadeInAlpha { true };
float _oculusUIRadius { 1.0f };
quint64 _fadeStarted { 0 };
float _fadeFailsafeEndValue { 1.0f };
void checkFadeFailsafe();
void startFadeFailsafe(float endValue);
int _reticleQuad;
float _hmdUIRadius { 1.0f };
int _previousBorderWidth { -1 };
int _previousBorderHeight { -1 };

View file

@ -215,9 +215,10 @@ OpenGLDisplayPlugin::OpenGLDisplayPlugin() {
}
void OpenGLDisplayPlugin::cleanupForSceneTexture(const gpu::TexturePointer& sceneTexture) {
Lock lock(_mutex);
Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture));
_sceneTextureToFrameIndexMap.remove(sceneTexture);
withRenderThreadLock([&] {
Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture));
_sceneTextureToFrameIndexMap.remove(sceneTexture);
});
}
@ -275,6 +276,15 @@ bool OpenGLDisplayPlugin::activate() {
_container->makeRenderingContextCurrent();
#endif
auto compositorHelper = DependencyManager::get<CompositorHelper>();
connect(compositorHelper.data(), &CompositorHelper::alphaChanged, [this] {
auto compositorHelper = DependencyManager::get<CompositorHelper>();
auto animation = new QPropertyAnimation(this, "overlayAlpha");
animation->setDuration(200);
animation->setEndValue(compositorHelper->getAlpha());
animation->start();
});
if (isHmd() && (getHmdScreen() >= 0)) {
_container->showDisplayPluginsTools();
}
@ -284,6 +294,9 @@ bool OpenGLDisplayPlugin::activate() {
void OpenGLDisplayPlugin::deactivate() {
auto compositorHelper = DependencyManager::get<CompositorHelper>();
disconnect(compositorHelper.data());
#if THREADED_PRESENT
auto presentThread = DependencyManager::get<PresentThread>();
// Does not return until the GL transition has completeed
@ -410,10 +423,9 @@ void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, const gpu::Tex
return;
}
{
Lock lock(_mutex);
withRenderThreadLock([&] {
_sceneTextureToFrameIndexMap[sceneTexture] = frameIndex;
}
});
// Submit it to the presentation thread via escrow
_sceneTextureEscrow.submit(sceneTexture);
@ -447,11 +459,12 @@ void OpenGLDisplayPlugin::updateTextures() {
}
void OpenGLDisplayPlugin::updateFrameData() {
Lock lock(_mutex);
auto previousFrameIndex = _currentPresentFrameIndex;
_currentPresentFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture];
auto skippedCount = (_currentPresentFrameIndex - previousFrameIndex) - 1;
_droppedFrameRate.increment(skippedCount);
withPresentThreadLock([&] {
auto previousFrameIndex = _currentPresentFrameIndex;
_currentPresentFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture];
auto skippedCount = (_currentPresentFrameIndex - previousFrameIndex) - 1;
_droppedFrameRate.increment(skippedCount);
});
}
void OpenGLDisplayPlugin::compositeOverlay() {
@ -460,25 +473,22 @@ void OpenGLDisplayPlugin::compositeOverlay() {
auto compositorHelper = DependencyManager::get<CompositorHelper>();
useProgram(_program);
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(_compositeOverlayAlpha);
// check the alpha
auto overlayAlpha = compositorHelper->getAlpha();
if (overlayAlpha > 0.0f) {
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(overlayAlpha);
// Overlay draw
if (isStereo()) {
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
for_each_eye([&](Eye eye) {
eyeViewport(eye);
drawUnitQuad();
});
} else {
// Overlay draw
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
// Overlay draw
if (isStereo()) {
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
for_each_eye([&](Eye eye) {
eyeViewport(eye);
drawUnitQuad();
}
});
} else {
// Overlay draw
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
drawUnitQuad();
}
// restore the alpha
Uniform<float>(*_program, _alphaUniform).Set(1.0);
}
@ -487,23 +497,19 @@ void OpenGLDisplayPlugin::compositePointer() {
auto compositorHelper = DependencyManager::get<CompositorHelper>();
useProgram(_program);
// check the alpha
auto overlayAlpha = compositorHelper->getAlpha();
if (overlayAlpha > 0.0f) {
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(overlayAlpha);
Uniform<glm::mat4>(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4()));
if (isStereo()) {
for_each_eye([&](Eye eye) {
eyeViewport(eye);
drawUnitQuad();
});
} else {
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(_compositeOverlayAlpha);
Uniform<glm::mat4>(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4()));
if (isStereo()) {
for_each_eye([&](Eye eye) {
eyeViewport(eye);
drawUnitQuad();
}
});
} else {
drawUnitQuad();
}
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
// restore the alpha
Uniform<float>(*_program, _alphaUniform).Set(1.0);
}
@ -523,14 +529,14 @@ void OpenGLDisplayPlugin::compositeLayers() {
}
_compositeFramebuffer->Bound(Framebuffer::Target::Draw, [&] {
Context::Viewport(targetRenderSize.x, targetRenderSize.y);
Context::Clear().DepthBuffer();
glBindTexture(GL_TEXTURE_2D, getSceneTextureId());
compositeScene();
auto sceneTextureId = getSceneTextureId();
auto overlayTextureId = getOverlayTextureId();
glBindTexture(GL_TEXTURE_2D, sceneTextureId);
compositeScene();
if (overlayTextureId) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, overlayTextureId);
Context::Enable(Capability::Blend);
Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha);
compositeOverlay();
auto compositorHelper = DependencyManager::get<CompositorHelper>();
@ -538,11 +544,16 @@ void OpenGLDisplayPlugin::compositeLayers() {
auto& cursorManager = Cursor::Manager::instance();
const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()];
glBindTexture(GL_TEXTURE_2D, cursorData.texture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, overlayTextureId);
compositePointer();
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
}
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_BLEND);
Context::Disable(Capability::Blend);
}
compositeExtra();
});
}
@ -580,7 +591,11 @@ float OpenGLDisplayPlugin::newFramePresentRate() const {
}
float OpenGLDisplayPlugin::droppedFrameRate() const {
return _droppedFrameRate.rate();
float result;
withRenderThreadLock([&] {
result = _droppedFrameRate.rate();
});
return result;
}
float OpenGLDisplayPlugin::presentRate() const {
@ -695,3 +710,18 @@ void OpenGLDisplayPlugin::useProgram(const ProgramPtr& program) {
_activeProgram = program;
}
}
void OpenGLDisplayPlugin::assertIsRenderThread() const {
Q_ASSERT(QThread::currentThread() != _presentThread);
}
void OpenGLDisplayPlugin::assertIsPresentThread() const {
Q_ASSERT(QThread::currentThread() == _presentThread);
}
bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
withRenderThreadLock([&] {
_compositeOverlayAlpha = _overlayAlpha;
});
return Parent::beginFrameRender(frameIndex);
}

View file

@ -24,7 +24,9 @@
#define THREADED_PRESENT 1
class OpenGLDisplayPlugin : public DisplayPlugin {
Q_OBJECT
Q_PROPERTY(float overlayAlpha MEMBER _overlayAlpha)
using Parent = DisplayPlugin;
protected:
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
@ -61,6 +63,7 @@ public:
float droppedFrameRate() const override;
bool beginFrameRender(uint32_t frameIndex) override;
protected:
#if THREADED_PRESENT
friend class PresentThread;
@ -75,6 +78,7 @@ protected:
virtual void compositeScene();
virtual void compositeOverlay();
virtual void compositePointer();
virtual void compositeExtra() {};
virtual bool hasFocus() const override;
@ -110,12 +114,12 @@ protected:
int32_t _alphaUniform { -1 };
ShapeWrapperPtr _plane;
mutable Mutex _mutex;
RateCounter<> _droppedFrameRate;
RateCounter<> _newFrameRate;
RateCounter<> _presentRate;
QMap<gpu::TexturePointer, uint32_t> _sceneTextureToFrameIndexMap;
uint32_t _currentPresentFrameIndex { 0 };
float _compositeOverlayAlpha{ 1.0f };
gpu::TexturePointer _currentSceneTexture;
gpu::TexturePointer _currentOverlayTexture;
@ -136,10 +140,28 @@ protected:
BasicFramebufferWrapperPtr _compositeFramebuffer;
bool _lockCurrentTexture { false };
void assertIsRenderThread() const;
void assertIsPresentThread() const;
template<typename F>
void withPresentThreadLock(F f) const {
assertIsPresentThread();
Lock lock(_presentMutex);
f();
}
template<typename F>
void withRenderThreadLock(F f) const {
assertIsRenderThread();
Lock lock(_presentMutex);
f();
}
private:
using Parent = DisplayPlugin;
// Any resource shared by the main thread and the presentation thread must
// be serialized through this mutex
mutable Mutex _presentMutex;
ProgramPtr _activeProgram;
float _overlayAlpha{ 1.0f };
};

View file

@ -9,6 +9,7 @@
#include <memory>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/intersect.hpp>
#include <QtCore/QLoggingCategory>
#include <QtWidgets/QApplication>
@ -37,7 +38,6 @@ QRect HmdDisplayPlugin::getRecommendedOverlayRect() const {
return CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT;
}
bool HmdDisplayPlugin::internalActivate() {
_monoPreview = _container->getBoolSetting("monoPreview", DEFAULT_MONO_VIEW);
@ -197,14 +197,43 @@ static ProgramPtr getReprojectionProgram() {
#endif
static const char * LASER_VS = R"VS(#version 410 core
uniform mat4 mvp = mat4(1);
in vec3 Position;
out vec3 vPosition;
void main() {
gl_Position = mvp * vec4(Position, 1);
vPosition = Position;
}
)VS";
static const char * LASER_FS = R"FS(#version 410 core
uniform vec4 color = vec4(1.0, 1.0, 1.0, 1.0);
in vec3 vPosition;
out vec4 FragColor;
void main() {
FragColor = color;
}
)FS";
void HmdDisplayPlugin::customizeContext() {
Parent::customizeContext();
// Only enable mirroring if we know vsync is disabled
enableVsync(false);
_enablePreview = !isVsyncEnabled();
_sphereSection = loadSphereSection(_program, CompositorHelper::VIRTUAL_UI_TARGET_FOV.y, CompositorHelper::VIRTUAL_UI_ASPECT_RATIO);
compileProgram(_laserProgram, LASER_VS, LASER_FS);
_laserGeometry = loadLaser(_laserProgram);
compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS);
using namespace oglplus;
REPROJECTION_MATRIX_LOCATION = Uniform<glm::mat3>(*_reprojectionProgram, "reprojection").Location();
INVERSE_PROJECTION_MATRIX_LOCATION = Uniform<glm::mat4>(*_reprojectionProgram, "inverseProjections").Location();
@ -215,6 +244,8 @@ void HmdDisplayPlugin::uncustomizeContext() {
_sphereSection.reset();
_compositeFramebuffer.reset();
_reprojectionProgram.reset();
_laserProgram.reset();
_laserGeometry.reset();
Parent::uncustomizeContext();
}
@ -253,23 +284,20 @@ void HmdDisplayPlugin::compositeScene() {
void HmdDisplayPlugin::compositeOverlay() {
using namespace oglplus;
auto compositorHelper = DependencyManager::get<CompositorHelper>();
glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix();
// check the alpha
useProgram(_program);
auto overlayAlpha = compositorHelper->getAlpha();
if (overlayAlpha > 0.0f) {
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(overlayAlpha);
_sphereSection->Use();
for_each_eye([&](Eye eye) {
eyeViewport(eye);
auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye));
auto mvp = _eyeProjections[eye] * modelView;
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
_sphereSection->Draw();
});
}
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(_compositeOverlayAlpha);
_sphereSection->Use();
for_each_eye([&](Eye eye) {
eyeViewport(eye);
auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat;
auto mvp = _eyeProjections[eye] * modelView;
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
_sphereSection->Draw();
});
// restore the alpha
Uniform<float>(*_program, _alphaUniform).Set(1.0);
}
@ -278,29 +306,27 @@ void HmdDisplayPlugin::compositePointer() {
auto compositorHelper = DependencyManager::get<CompositorHelper>();
// check the alpha
useProgram(_program);
auto overlayAlpha = compositorHelper->getAlpha();
if (overlayAlpha > 0.0f) {
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(overlayAlpha);
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(_compositeOverlayAlpha);
// Mouse pointer
_plane->Use();
// Reconstruct the headpose from the eye poses
auto headPosition = vec3(_currentPresentFrameInfo.presentPose[3]);
for_each_eye([&](Eye eye) {
eyeViewport(eye);
auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye);
auto reticleTransform = compositorHelper->getReticleTransform(eyePose, headPosition);
auto mvp = _eyeProjections[eye] * reticleTransform;
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
_plane->Draw();
});
}
// Mouse pointer
_plane->Use();
// Reconstruct the headpose from the eye poses
auto headPosition = vec3(_currentPresentFrameInfo.presentPose[3]);
for_each_eye([&](Eye eye) {
eyeViewport(eye);
auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye);
auto reticleTransform = compositorHelper->getReticleTransform(eyePose, headPosition);
auto mvp = _eyeProjections[eye] * reticleTransform;
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
_plane->Draw();
});
// restore the alpha
Uniform<float>(*_program, _alphaUniform).Set(1.0);
}
void HmdDisplayPlugin::internalPresent() {
PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)presentCount())
@ -357,22 +383,117 @@ void HmdDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm:
void HmdDisplayPlugin::updateFrameData() {
// Check if we have old frame data to discard
{
Lock lock(_mutex);
withPresentThreadLock([&] {
auto itr = _frameInfos.find(_currentPresentFrameIndex);
if (itr != _frameInfos.end()) {
_frameInfos.erase(itr);
}
}
});
Parent::updateFrameData();
{
Lock lock(_mutex);
withPresentThreadLock([&] {
_currentPresentFrameInfo = _frameInfos[_currentPresentFrameIndex];
}
});
}
glm::mat4 HmdDisplayPlugin::getHeadPose() const {
return _currentRenderFrameInfo.renderPose;
}
bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) {
HandLaserInfo info;
info.mode = mode;
info.color = color;
info.direction = direction;
withRenderThreadLock([&] {
if (hands & Hand::LeftHand) {
_handLasers[0] = info;
}
if (hands & Hand::RightHand) {
_handLasers[1] = info;
}
});
// FIXME defer to a child class plugin to determine if hand lasers are actually
// available based on the presence or absence of hand controllers
return true;
}
void HmdDisplayPlugin::compositeExtra() {
const int NUMBER_OF_HANDS = 2;
std::array<HandLaserInfo, NUMBER_OF_HANDS> handLasers;
std::array<mat4, NUMBER_OF_HANDS> renderHandPoses;
Transform uiModelTransform;
withPresentThreadLock([&] {
handLasers = _handLasers;
renderHandPoses = _handPoses;
uiModelTransform = _uiModelTransform;
});
// If neither hand laser is activated, exit
if (!handLasers[0].valid() && !handLasers[1].valid()) {
return;
}
static const glm::mat4 identity;
if (renderHandPoses[0] == identity && renderHandPoses[1] == identity) {
return;
}
// Render hand lasers
using namespace oglplus;
useProgram(_laserProgram);
_laserGeometry->Use();
std::array<mat4, NUMBER_OF_HANDS> handLaserModelMatrices;
for (int i = 0; i < NUMBER_OF_HANDS; ++i) {
if (renderHandPoses[i] == identity) {
continue;
}
const auto& handLaser = handLasers[i];
if (!handLaser.valid()) {
continue;
}
const auto& laserDirection = handLaser.direction;
auto model = renderHandPoses[i];
auto castDirection = glm::quat_cast(model) * laserDirection;
if (glm::abs(glm::length2(castDirection) - 1.0f) > EPSILON) {
castDirection = glm::normalize(castDirection);
}
// FIXME fetch the actual UI radius from... somewhere?
float uiRadius = 1.0f;
// Find the intersection of the laser with he UI and use it to scale the model matrix
float distance;
if (!glm::intersectRaySphere(vec3(renderHandPoses[i][3]), castDirection, uiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) {
continue;
}
// Make sure we rotate to match the desired laser direction
if (laserDirection != Vectors::UNIT_NEG_Z) {
auto rotation = glm::rotation(Vectors::UNIT_NEG_Z, laserDirection);
model = model * glm::mat4_cast(rotation);
}
model = glm::scale(model, vec3(distance));
handLaserModelMatrices[i] = model;
}
for_each_eye([&](Eye eye) {
eyeViewport(eye);
auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye);
auto view = glm::inverse(eyePose);
const auto& projection = _eyeProjections[eye];
for (int i = 0; i < NUMBER_OF_HANDS; ++i) {
if (handLaserModelMatrices[i] == identity) {
continue;
}
Uniform<glm::mat4>(*_laserProgram, "mvp").Set(projection * view * handLaserModelMatrices[i]);
Uniform<glm::vec4>(*_laserProgram, "color").Set(handLasers[i].color);
_laserGeometry->Draw();
// TODO render some kind of visual indicator at the intersection point with the UI.
}
});
}

View file

@ -10,6 +10,7 @@
#include <ThreadSafeValueCache.h>
#include <QtGlobal>
#include <Transform.h>
#include "../OpenGLDisplayPlugin.h"
@ -30,7 +31,7 @@ public:
virtual glm::mat4 getHeadPose() const override;
bool setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) override;
protected:
virtual void hmdPresent() = 0;
@ -46,7 +47,22 @@ protected:
void customizeContext() override;
void uncustomizeContext() override;
void updateFrameData() override;
void compositeExtra() override;
struct HandLaserInfo {
HandLaserMode mode { HandLaserMode::None };
vec4 color { 1.0f };
vec3 direction { 0, 0, -1 };
// Is this hand laser info suitable for drawing?
bool valid() const {
return (mode != HandLaserMode::None && color.a > 0.0f && direction != vec3());
}
};
Transform _uiModelTransform;
std::array<HandLaserInfo, 2> _handLasers;
std::array<glm::mat4, 2> _handPoses;
std::array<glm::mat4, 2> _eyeOffsets;
std::array<glm::mat4, 2> _eyeProjections;
std::array<glm::mat4, 2> _eyeInverseProjections;
@ -75,5 +91,7 @@ private:
bool _enableReprojection { true };
ShapeWrapperPtr _sphereSection;
ProgramPtr _reprojectionProgram;
ProgramPtr _laserProgram;
ShapeWrapperPtr _laserGeometry;
};

View file

@ -593,23 +593,25 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
// the model is still being downloaded.
return false;
} else if (type == SHAPE_TYPE_STATIC_MESH) {
return (_model && _model->isLoaded());
}
return true;
}
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
ShapeType type = getShapeType();
glm::vec3 dimensions = getDimensions();
if (type == SHAPE_TYPE_COMPOUND) {
updateModelBounds();
// should never fall in here when collision model not fully loaded
// hence we assert that all geometries exist and are loaded
assert(_model->isLoaded() && _model->isCollisionLoaded());
const FBXGeometry& renderGeometry = _model->getFBXGeometry();
const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry();
QVector<QVector<glm::vec3>>& points = info.getPoints();
points.clear();
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
pointCollection.clear();
uint32_t i = 0;
// the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect
@ -619,8 +621,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
foreach (const FBXMesh& mesh, collisionGeometry.meshes) {
// each meshPart is a convex hull
foreach (const FBXMeshPart &meshPart, mesh.parts) {
points.push_back(QVector<glm::vec3>());
QVector<glm::vec3>& pointsInPart = points[i];
pointCollection.push_back(QVector<glm::vec3>());
ShapeInfo::PointList& pointsInPart = pointCollection[i];
// run through all the triangles and (uniquely) add each point to the hull
uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size();
@ -664,7 +666,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
if (pointsInPart.size() == 0) {
qCDebug(entitiesrenderer) << "Warning -- meshPart has no faces";
points.pop_back();
pointCollection.pop_back();
continue;
}
++i;
@ -677,29 +679,136 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
// to the visual model and apply them to the collision model (without regard for the
// collision model's extents).
glm::vec3 scale = getDimensions() / renderGeometry.getUnscaledMeshExtents().size();
glm::vec3 scaleToFit = dimensions / _model->getFBXGeometry().getUnscaledMeshExtents().size();
// multiply each point by scale before handing the point-set off to the physics engine.
// also determine the extents of the collision model.
AABox box;
for (int i = 0; i < points.size(); i++) {
for (int j = 0; j < points[i].size(); j++) {
for (int i = 0; i < pointCollection.size(); i++) {
for (int j = 0; j < pointCollection[i].size(); j++) {
// compensate for registration
points[i][j] += _model->getOffset();
pointCollection[i][j] += _model->getOffset();
// scale so the collision points match the model points
points[i][j] *= scale;
// this next subtraction is done so we can give info the offset, which will cause
// the shape-key to change.
points[i][j] -= _model->getOffset();
box += points[i][j];
pointCollection[i][j] *= scaleToFit;
}
}
info.setParams(type, dimensions, _compoundShapeURL);
} else if (type == SHAPE_TYPE_STATIC_MESH) {
// compute meshPart local transforms
QVector<glm::mat4> localTransforms;
const FBXGeometry& geometry = _model->getFBXGeometry();
int numberOfMeshes = geometry.meshes.size();
for (int i = 0; i < numberOfMeshes; i++) {
const FBXMesh& mesh = geometry.meshes.at(i);
if (mesh.clusters.size() > 0) {
const FBXCluster& cluster = mesh.clusters.at(0);
auto jointMatrix = _model->getRig()->getJointTransform(cluster.jointIndex);
localTransforms.push_back(jointMatrix * cluster.inverseBindMatrix);
} else {
glm::mat4 identity;
localTransforms.push_back(identity);
}
}
glm::vec3 collisionModelDimensions = box.getDimensions();
info.setParams(type, collisionModelDimensions, _compoundShapeURL);
info.setOffset(_model->getOffset());
updateModelBounds();
// should never fall in here when collision model not fully loaded
assert(_model->isLoaded());
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
pointCollection.clear();
ShapeInfo::PointList points;
ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
auto& meshes = _model->getGeometry()->getGeometry()->getMeshes();
Extents extents;
int meshCount = 0;
for (auto& mesh : meshes) {
const gpu::BufferView& vertices = mesh->getVertexBuffer();
const gpu::BufferView& indices = mesh->getIndexBuffer();
const gpu::BufferView& parts = mesh->getPartBuffer();
// copy points
const glm::mat4& localTransform = localTransforms[meshCount];
uint32_t meshIndexOffset = (uint32_t)points.size();
gpu::BufferView::Iterator<const glm::vec3> vertexItr = vertices.cbegin<const glm::vec3>();
points.reserve((int32_t)((gpu::Size)points.size() + vertices.getNumElements()));
while (vertexItr != vertices.cend<const glm::vec3>()) {
glm::vec3 point = extractTranslation(localTransform * glm::translate(*vertexItr));
points.push_back(point);
extents.addPoint(point);
++vertexItr;
}
// copy triangleIndices
triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.getNumElements()));
gpu::BufferView::Iterator<const model::Mesh::Part> partItr = parts.cbegin<const model::Mesh::Part>();
while (partItr != parts.cend<const model::Mesh::Part>()) {
if (partItr->_topology == model::Mesh::TRIANGLES) {
assert(partItr->_numIndices % 3 == 0);
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
auto indexEnd = indexItr + partItr->_numIndices;
while (indexItr != indexEnd) {
triangleIndices.push_back(*indexItr + meshIndexOffset);
++indexItr;
}
} else if (partItr->_topology == model::Mesh::TRIANGLE_STRIP) {
assert(partItr->_numIndices > 2);
uint32_t approxNumIndices = 3 * partItr->_numIndices;
if (approxNumIndices > (uint32_t)(triangleIndices.capacity() - triangleIndices.size())) {
// we underestimated the final size of triangleIndices so we pre-emptively expand it
triangleIndices.reserve(triangleIndices.size() + approxNumIndices);
}
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
auto indexEnd = indexItr + (partItr->_numIndices - 2);
// first triangle uses the first three indices
triangleIndices.push_back(*(indexItr++) + meshIndexOffset);
triangleIndices.push_back(*(indexItr++) + meshIndexOffset);
triangleIndices.push_back(*(indexItr++) + meshIndexOffset);
// the rest use previous and next index
uint32_t triangleCount = 1;
while (indexItr != indexEnd) {
if ((*indexItr) != model::Mesh::PRIMITIVE_RESTART_INDEX) {
if (triangleCount % 2 == 0) {
// even triangles use first two indices in order
triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset);
triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset);
} else {
// odd triangles swap order of first two indices
triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset);
triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset);
}
triangleIndices.push_back(*indexItr + meshIndexOffset);
++triangleCount;
}
++indexItr;
}
}
++partItr;
}
++meshCount;
}
// scale and shift
glm::vec3 extentsSize = extents.size();
glm::vec3 scaleToFit = dimensions / extentsSize;
for (int i = 0; i < 3; ++i) {
if (extentsSize[i] < 1.0e-6f) {
scaleToFit[i] = 1.0f;
}
}
for (int i = 0; i < points.size(); ++i) {
points[i] = (points[i] * scaleToFit);
}
pointCollection.push_back(points);
info.setParams(SHAPE_TYPE_STATIC_MESH, 0.5f * dimensions, _modelURL);
} else {
ModelEntityItem::computeShapeInfo(info);
info.setParams(type, 0.5f * getDimensions());
info.setParams(type, 0.5f * dimensions);
adjustShapeInfoByRegistration(info);
}
}

View file

@ -1198,7 +1198,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
QtConcurrent::run([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] {
auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity);
QVector<QVector<glm::vec3>> points;
QVector<QVector<glm::vec3>> pointCollection;
AABox box;
glm::mat4 vtoM = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity)->voxelToLocalMatrix();
@ -1207,7 +1207,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
// pull each triangle in the mesh into a polyhedron which can be collided with
unsigned int i = 0;
const gpu::BufferView vertexBufferView = mesh->getVertexBuffer();
const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer();
const gpu::BufferView& indexBufferView = mesh->getIndexBuffer();
gpu::BufferView::Iterator<const uint32_t> it = indexBufferView.cbegin<uint32_t>();
@ -1241,9 +1241,9 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
pointsInPart << p3Model;
// add next convex hull
QVector<glm::vec3> newMeshPoints;
points << newMeshPoints;
pointCollection << newMeshPoints;
// add points to the new convex hull
points[i++] << pointsInPart;
pointCollection[i++] << pointsInPart;
}
} else {
unsigned int i = 0;
@ -1299,19 +1299,19 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
// add next convex hull
QVector<glm::vec3> newMeshPoints;
points << newMeshPoints;
pointCollection << newMeshPoints;
// add points to the new convex hull
points[i++] << pointsInPart;
pointCollection[i++] << pointsInPart;
}
});
}
polyVoxEntity->setCollisionPoints(points, box);
polyVoxEntity->setCollisionPoints(pointCollection, box);
});
}
void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector<QVector<glm::vec3>> points, AABox box) {
void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection pointCollection, AABox box) {
// this catches the payload from computeShapeInfoWorker
if (points.isEmpty()) {
if (pointCollection.isEmpty()) {
EntityItem::computeShapeInfo(_shapeInfo);
return;
}
@ -1325,7 +1325,7 @@ void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector<QVector<glm::
QString::number(_registrationPoint.y) + "," +
QString::number(_registrationPoint.z);
_shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey);
_shapeInfo.setConvexHulls(points);
_shapeInfo.setPointCollection(pointCollection);
_meshDirty = false;
});
}

View file

@ -123,7 +123,7 @@ public:
std::function<void(int, int, int, uint8_t)> thunk);
void setMesh(model::MeshPointer mesh);
void setCollisionPoints(const QVector<QVector<glm::vec3>> points, AABox box);
void setCollisionPoints(ShapeInfo::PointCollection points, AABox box);
PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; }
uint8_t getVoxelInternal(int x, int y, int z);

View file

@ -1602,14 +1602,20 @@ void EntityItem::updateMass(float mass) {
void EntityItem::updateVelocity(const glm::vec3& value) {
glm::vec3 velocity = getLocalVelocity();
if (velocity != value) {
const float MIN_LINEAR_SPEED = 0.001f;
if (glm::length(value) < MIN_LINEAR_SPEED) {
velocity = ENTITY_ITEM_ZERO_VEC3;
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
if (velocity != Vectors::ZERO) {
setLocalVelocity(Vectors::ZERO);
}
} else {
velocity = value;
const float MIN_LINEAR_SPEED = 0.001f;
if (glm::length(value) < MIN_LINEAR_SPEED) {
velocity = ENTITY_ITEM_ZERO_VEC3;
} else {
velocity = value;
}
setLocalVelocity(velocity);
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
}
setLocalVelocity(velocity);
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
}
}
@ -1630,22 +1636,30 @@ void EntityItem::updateDamping(float value) {
void EntityItem::updateGravity(const glm::vec3& value) {
if (_gravity != value) {
_gravity = value;
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
_gravity = Vectors::ZERO;
} else {
_gravity = value;
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
}
}
}
void EntityItem::updateAngularVelocity(const glm::vec3& value) {
glm::vec3 angularVelocity = getLocalAngularVelocity();
if (angularVelocity != value) {
const float MIN_ANGULAR_SPEED = 0.0002f;
if (glm::length(value) < MIN_ANGULAR_SPEED) {
angularVelocity = ENTITY_ITEM_ZERO_VEC3;
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
setLocalAngularVelocity(Vectors::ZERO);
} else {
angularVelocity = value;
const float MIN_ANGULAR_SPEED = 0.0002f;
if (glm::length(value) < MIN_ANGULAR_SPEED) {
angularVelocity = ENTITY_ITEM_ZERO_VEC3;
} else {
angularVelocity = value;
}
setLocalAngularVelocity(angularVelocity);
_dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY;
}
setLocalAngularVelocity(angularVelocity);
_dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY;
}
}
@ -1679,9 +1693,17 @@ void EntityItem::updateCollisionMask(uint8_t value) {
}
void EntityItem::updateDynamic(bool value) {
if (_dynamic != value) {
_dynamic = value;
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
if (getDynamic() != value) {
// dynamic and STATIC_MESH are incompatible so we check for that case
if (value && getShapeType() == SHAPE_TYPE_STATIC_MESH) {
if (_dynamic) {
_dynamic = false;
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
}
} else {
_dynamic = value;
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
}
}
}
@ -1731,7 +1753,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
group = BULLET_COLLISION_GROUP_COLLISIONLESS;
mask = 0;
} else {
if (_dynamic) {
if (getDynamic()) {
group = BULLET_COLLISION_GROUP_DYNAMIC;
} else if (isMovingRelativeToParent() || hasActions()) {
group = BULLET_COLLISION_GROUP_KINEMATIC;

View file

@ -283,7 +283,7 @@ public:
void computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask) const;
bool getDynamic() const { return _dynamic; }
bool getDynamic() const { return SHAPE_TYPE_STATIC_MESH == getShapeType() ? false : _dynamic; }
void setDynamic(bool value) { _dynamic = value; }
virtual bool shouldBePhysical() const { return false; }
@ -348,7 +348,7 @@ public:
void updateDynamic(bool value);
void updateLifetime(float value);
void updateCreated(uint64_t value);
virtual void updateShapeType(ShapeType type) { /* do nothing */ }
virtual void setShapeType(ShapeType type) { /* do nothing */ }
uint32_t getDirtyFlags() const { return _dirtyFlags; }
void clearDirtyFlags(uint32_t mask = 0xffffffff) { _dirtyFlags &= ~mask; }

View file

@ -88,8 +88,21 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) {
_lastEdited = usecTime > _created ? usecTime : _created;
}
const char* shapeTypeNames[] = {"none", "box", "sphere", "plane", "compound", "capsule-x",
"capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"};
const char* shapeTypeNames[] = {
"none",
"box",
"sphere",
"capsule-x",
"capsule-y",
"capsule-z",
"cylinder-x",
"cylinder-y",
"cylinder-z",
"hull",
"plane",
"compound",
"static-mesh"
};
QHash<QString, ShapeType> stringToShapeTypeLookup;
@ -101,14 +114,16 @@ void buildStringToShapeTypeLookup() {
addShapeType(SHAPE_TYPE_NONE);
addShapeType(SHAPE_TYPE_BOX);
addShapeType(SHAPE_TYPE_SPHERE);
addShapeType(SHAPE_TYPE_PLANE);
addShapeType(SHAPE_TYPE_COMPOUND);
addShapeType(SHAPE_TYPE_CAPSULE_X);
addShapeType(SHAPE_TYPE_CAPSULE_Y);
addShapeType(SHAPE_TYPE_CAPSULE_Z);
addShapeType(SHAPE_TYPE_CYLINDER_X);
addShapeType(SHAPE_TYPE_CYLINDER_Y);
addShapeType(SHAPE_TYPE_CYLINDER_Z);
addShapeType(SHAPE_TYPE_HULL);
addShapeType(SHAPE_TYPE_PLANE);
addShapeType(SHAPE_TYPE_COMPOUND);
addShapeType(SHAPE_TYPE_STATIC_MESH);
}
QString getCollisionGroupAsString(uint8_t group) {

View file

@ -77,7 +77,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotationsSet, setJointRotationsSet);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotations, setJointRotations);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslationsSet, setJointTranslationsSet);
@ -145,7 +145,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
dataAt += bytesFromAnimation;
}
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
if (animationPropertiesChanged) {
_dirtyFlags |= Simulation::DIRTY_UPDATEABLE;
@ -257,37 +257,54 @@ void ModelEntityItem::debugDump() const {
qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL();
}
void ModelEntityItem::updateShapeType(ShapeType type) {
// BEGIN_TEMPORARY_WORKAROUND
// we have allowed inconsistent ShapeType's to be stored in SVO files in the past (this was a bug)
// but we are now enforcing the entity properties to be consistent. To make the possible we're
// introducing a temporary workaround: we will ignore ShapeType updates that conflict with the
// _compoundShapeURL.
if (hasCompoundShapeURL()) {
type = SHAPE_TYPE_COMPOUND;
}
// END_TEMPORARY_WORKAROUND
void ModelEntityItem::setShapeType(ShapeType type) {
if (type != _shapeType) {
if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) {
// dynamic and STATIC_MESH are incompatible
// since the shape is being set here we clear the dynamic bit
_dynamic = false;
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
}
_shapeType = type;
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
}
// virtual
ShapeType ModelEntityItem::getShapeType() const {
if (_shapeType == SHAPE_TYPE_COMPOUND) {
return hasCompoundShapeURL() ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE;
} else {
return _shapeType;
return computeTrueShapeType();
}
ShapeType ModelEntityItem::computeTrueShapeType() const {
ShapeType type = _shapeType;
if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) {
// dynamic is incompatible with STATIC_MESH
// shouldn't fall in here but just in case --> fall back to COMPOUND
type = SHAPE_TYPE_COMPOUND;
}
if (type == SHAPE_TYPE_COMPOUND && !hasCompoundShapeURL()) {
// no compoundURL set --> fall back to NONE
type = SHAPE_TYPE_NONE;
}
return type;
}
void ModelEntityItem::setModelURL(const QString& url) {
if (_modelURL != url) {
_modelURL = url;
_parsedModelURL = QUrl(url);
if (_shapeType == SHAPE_TYPE_STATIC_MESH) {
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
}
}
void ModelEntityItem::setCompoundShapeURL(const QString& url) {
if (_compoundShapeURL != url) {
ShapeType oldType = computeTrueShapeType();
_compoundShapeURL = url;
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
_shapeType = _compoundShapeURL.isEmpty() ? SHAPE_TYPE_NONE : SHAPE_TYPE_COMPOUND;
if (oldType != computeTrueShapeType()) {
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
}
}

View file

@ -50,9 +50,10 @@ public:
virtual bool needsToCallUpdate() const;
virtual void debugDump() const;
void updateShapeType(ShapeType type);
void setShapeType(ShapeType type);
virtual ShapeType getShapeType() const;
// TODO: Move these to subclasses, or other appropriate abstraction
// getters/setters applicable to models and particles
@ -76,7 +77,7 @@ public:
}
// model related properties
virtual void setModelURL(const QString& url) { _modelURL = url; _parsedModelURL = QUrl(url); }
virtual void setModelURL(const QString& url);
virtual void setCompoundShapeURL(const QString& url);
// Animation related items...
@ -130,6 +131,7 @@ public:
private:
void setAnimationSettings(const QString& value); // only called for old bitstream format
ShapeType computeTrueShapeType() const;
protected:
// these are used:

View file

@ -342,7 +342,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(isEmitting, setIsEmitting);
@ -406,7 +406,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
READ_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, bool, setIsEmitting);
}
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles);
READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, setLifespan);
READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, setEmitRate);
@ -584,7 +584,7 @@ void ParticleEffectEntityItem::debugDump() const {
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
}
void ParticleEffectEntityItem::updateShapeType(ShapeType type) {
void ParticleEffectEntityItem::setShapeType(ShapeType type) {
if (type != _shapeType) {
_shapeType = type;
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;

View file

@ -95,7 +95,7 @@ public:
void setAlphaSpread(float alphaSpread);
float getAlphaSpread() const { return _alphaSpread; }
void updateShapeType(ShapeType type);
void setShapeType(ShapeType type);
virtual ShapeType getShapeType() const { return _shapeType; }
virtual void debugDump() const;

View file

@ -73,7 +73,7 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) {
bool somethingChangedInStage = _stageProperties.setProperties(properties);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundMode, setBackgroundMode);
@ -117,7 +117,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
bytesRead += bytesFromStage;
dataAt += bytesFromStage;
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode);

View file

@ -55,7 +55,7 @@ public:
static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; }
virtual bool isReadyToComputeShape() { return false; }
void updateShapeType(ShapeType type) { _shapeType = type; }
void setShapeType(ShapeType type) { _shapeType = type; }
virtual ShapeType getShapeType() const;
virtual bool hasCompoundShapeURL() const { return !_compoundShapeURL.isEmpty(); }

View file

@ -5,6 +5,7 @@
#include <QtGui/QSurfaceFormat>
#include <QtOpenGL/QGL>
#include <QOpenGLContext>
#include <QtCore/QRegularExpression>
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
static QSurfaceFormat format;
@ -39,6 +40,13 @@ const QGLFormat& getDefaultGLFormat() {
return glFormat;
}
int glVersionToInteger(QString glVersion) {
QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]"));
int majorNumber = versionParts[0].toInt();
int minorNumber = versionParts[1].toInt();
return majorNumber * 100 + minorNumber * 10;
}
QJsonObject getGLContextData() {
if (!QOpenGLContext::currentContext()) {
return QJsonObject();

View file

@ -27,5 +27,6 @@ void setGLFormatVersion(F& format, int major = 4, int minor = 5) { format.setVer
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat();
const QGLFormat& getDefaultGLFormat();
QJsonObject getGLContextData();
int glVersionToInteger(QString glVersion);
#endif

View file

@ -396,6 +396,8 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
_renderer->_renderControl->_renderWindow = _proxyWindow;
connect(_renderer->_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged);
// Create a QML engine.
_qmlEngine = new QQmlEngine;
if (!_qmlEngine->incubationController()) {
@ -742,3 +744,21 @@ QVariant OffscreenQmlSurface::returnFromUiThread(std::function<QVariant()> funct
return function();
}
void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) {
if (!object) {
setFocusText(false);
return;
}
QInputMethodQueryEvent query(Qt::ImEnabled);
qApp->sendEvent(object, &query);
setFocusText(query.value(Qt::ImEnabled).toBool());
}
void OffscreenQmlSurface::setFocusText(bool newFocusText) {
if (newFocusText != _focusText) {
_focusText = newFocusText;
emit focusTextChanged(_focusText);
}
}

View file

@ -30,7 +30,7 @@ class OffscreenQmlRenderThread;
class OffscreenQmlSurface : public QObject {
Q_OBJECT
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
public:
OffscreenQmlSurface();
virtual ~OffscreenQmlSurface();
@ -55,6 +55,7 @@ public:
_mouseTranslator = mouseTranslator;
}
bool isFocusText() const { return _focusText; }
void pause();
void resume();
bool isPaused() const;
@ -70,6 +71,8 @@ public:
signals:
void textureUpdated(unsigned int texture);
void focusObjectChanged(QObject* newFocus);
void focusTextChanged(bool focusText);
public slots:
void requestUpdate();
@ -78,6 +81,7 @@ public slots:
protected:
bool filterEnabled(QObject* originalDestination, QEvent* event) const;
void setFocusText(bool newFocusText);
private:
QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f);
@ -85,6 +89,7 @@ private:
private slots:
void updateQuick();
void onFocusObjectChanged(QObject* newFocus);
private:
friend class OffscreenQmlRenderThread;
@ -97,6 +102,7 @@ private:
bool _render{ false };
bool _polish{ true };
bool _paused{ true };
bool _focusText { false };
uint8_t _maxFps{ 60 };
MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } };
QWindow* _proxyWindow { nullptr };

View file

@ -45,9 +45,11 @@ in vec2 vTexCoord;
out vec4 FragColor;
void main() {
FragColor = texture(sampler, vTexCoord);
FragColor.a *= alpha;
if (FragColor.a <= 0.0) {
discard;
}
}
)FS";
@ -359,6 +361,94 @@ ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov, float aspect, i
);
}
namespace oglplus {
namespace shapes {
class Laser : public DrawingInstructionWriter, public DrawMode {
public:
using IndexArray = std::vector<GLuint>;
using PosArray = std::vector<float>;
/// The type of the index container returned by Indices()
// vertex positions
PosArray _pos_data;
IndexArray _idx_data;
unsigned int _prim_count { 0 };
public:
Laser() {
int vertices = 2;
_pos_data.resize(vertices * 3);
_pos_data[0] = 0;
_pos_data[1] = 0;
_pos_data[2] = 0;
_pos_data[3] = 0;
_pos_data[4] = 0;
_pos_data[5] = -1;
_idx_data.push_back(0);
_idx_data.push_back(1);
_prim_count = 1;
}
/// Returns the winding direction of faces
FaceOrientation FaceWinding(void) const {
return FaceOrientation::CCW;
}
/// Queries the bounding sphere coordinates and dimensions
template <typename T>
void BoundingSphere(Sphere<T>& bounding_sphere) const {
bounding_sphere = Sphere<T>(0, 0, -0.5, 0.5);
}
typedef GLuint(Laser::*VertexAttribFunc)(std::vector<GLfloat>&) const;
/// Makes the vertex positions and returns the number of values per vertex
template <typename T>
GLuint Positions(std::vector<T>& dest) const {
dest.clear();
dest.insert(dest.begin(), _pos_data.begin(), _pos_data.end());
return 3;
}
typedef VertexAttribsInfo<
Laser,
std::tuple<VertexPositionsTag>
> VertexAttribs;
/// Returns element indices that are used with the drawing instructions
const IndexArray & Indices(Default = Default()) const {
return _idx_data;
}
/// Returns the instructions for rendering of faces
DrawingInstructions Instructions(PrimitiveType primitive) const {
DrawingInstructions instr = MakeInstructions();
DrawOperation operation;
operation.method = DrawOperation::Method::DrawElements;
operation.mode = primitive;
operation.first = 0;
operation.count = _prim_count * 3;
operation.restart_index = DrawOperation::NoRestartIndex();
operation.phase = 0;
AddInstruction(instr, operation);
return instr;
}
/// Returns the instructions for rendering of faces
DrawingInstructions Instructions(Default = Default()) const {
return Instructions(PrimitiveType::Lines);
}
};
}
}
ShapeWrapperPtr loadLaser(const ProgramPtr& program) {
return std::make_shared<shapes::ShapeWrapper>(shapes::ShapeWrapper("Position", shapes::Laser(), *program));
}
void TextureRecycler::setSize(const uvec2& size) {
if (size == _size) {
return;

View file

@ -64,8 +64,9 @@ ProgramPtr loadCubemapShader();
void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& fs);
ShapeWrapperPtr loadSkybox(ProgramPtr program);
ShapeWrapperPtr loadPlane(ProgramPtr program, float aspect = 1.0f);
ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 32, int stacks = 32);
ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 128, int stacks = 128);
ShapeWrapperPtr loadLaser(const ProgramPtr& program);
// A basic wrapper for constructing a framebuffer with a renderbuffer
// for the depth attachment and an undefined type for the color attachement

View file

@ -44,6 +44,7 @@ Q_DECLARE_METATYPE(QNetworkAccessManager::Operation)
Q_DECLARE_METATYPE(JSONCallbackParameters)
const QString ACCOUNTS_GROUP = "accounts";
static const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit();
JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod,
QObject* errorCallbackReceiver, const QString& errorCallbackMethod,
@ -222,8 +223,7 @@ void AccountManager::sendRequest(const QString& path,
// if we're allowed to send usage data, include whatever the current session ID is with this request
auto& activityLogger = UserActivityLogger::getInstance();
if (activityLogger.isEnabled()) {
static const QString METAVERSE_SESSION_ID_HEADER = "HFM-SessionID";
networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER.toLocal8Bit(),
networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER,
uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit());
}
@ -322,6 +322,9 @@ void AccountManager::processReply() {
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
if (requestReply->error() == QNetworkReply::NoError) {
if (requestReply->hasRawHeader(METAVERSE_SESSION_ID_HEADER)) {
_sessionID = requestReply->rawHeader(METAVERSE_SESSION_ID_HEADER);
}
passSuccessToCallback(requestReply);
} else {
passErrorToCallback(requestReply);

View file

@ -26,9 +26,9 @@
class JSONCallbackParameters {
public:
JSONCallbackParameters(QObject* jsonCallbackReceiver = NULL, const QString& jsonCallbackMethod = QString(),
QObject* errorCallbackReceiver = NULL, const QString& errorCallbackMethod = QString(),
QObject* updateReceiver = NULL, const QString& updateSlot = QString());
JSONCallbackParameters(QObject* jsonCallbackReceiver = nullptr, const QString& jsonCallbackMethod = QString(),
QObject* errorCallbackReceiver = nullptr, const QString& errorCallbackMethod = QString(),
QObject* updateReceiver = nullptr, const QString& updateSlot = QString());
bool isEmpty() const { return !jsonCallbackReceiver && !errorCallbackReceiver; }
@ -86,6 +86,7 @@ public:
static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply);
QUuid getSessionID() const { return _sessionID; }
void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; }
public slots:
@ -139,7 +140,7 @@ private:
bool _isWaitingForKeypairResponse { false };
QByteArray _pendingPrivateKey;
QUuid _sessionID;
QUuid _sessionID { QUuid::createUuid() };
};
#endif // hifi_AccountManager_h

View file

@ -23,6 +23,7 @@
#include "AddressManager.h"
#include "NodeList.h"
#include "NetworkLogging.h"
#include "UserActivityLogger.h"
const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager";
@ -130,6 +131,10 @@ const JSONCallbackParameters& AddressManager::apiCallbackParameters() {
}
bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
static QString URL_TYPE_USER = "user";
static QString URL_TYPE_DOMAIN_ID = "domain_id";
static QString URL_TYPE_PLACE = "place";
static QString URL_TYPE_NETWORK_ADDRESS = "network_address";
if (lookupUrl.scheme() == HIFI_URL_SCHEME) {
qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString();
@ -147,6 +152,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
if (handleUsername(lookupUrl.authority())) {
// handled a username for lookup
UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_USER, lookupUrl.toString());
// in case we're failing to connect to where we thought this user was
// store their username as previous lookup so we can refresh their location via API
_previousLookup = lookupUrl;
@ -157,6 +164,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
if (handleNetworkAddress(lookupUrl.host()
+ (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) {
UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_NETWORK_ADDRESS, lookupUrl.toString());
// a network address lookup clears the previous lookup since we don't expect to re-attempt it
_previousLookup.clear();
@ -174,6 +183,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
// we may have a path that defines a relative viewpoint - if so we should jump to that now
handlePath(path, trigger);
} else if (handleDomainID(lookupUrl.host())){
UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_DOMAIN_ID, lookupUrl.toString());
// store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info
_previousLookup = lookupUrl;
@ -181,6 +192,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
// try to look up the domain ID on the metaverse API
attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger);
} else {
UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_PLACE, lookupUrl.toString());
// store this place name as the previous lookup in case we fail to connect and want to refresh API info
_previousLookup = lookupUrl;

View file

@ -18,6 +18,7 @@
#include "UserActivityLogger.h"
#include <DependencyManager.h>
#include "AddressManager.h"
static const QString USER_ACTIVITY_URL = "/api/v1/user_activities";
@ -125,6 +126,19 @@ void UserActivityLogger::changedDomain(QString domainURL) {
}
void UserActivityLogger::connectedDevice(QString typeOfDevice, QString deviceName) {
static QStringList DEVICE_BLACKLIST = {
"Desktop",
"NullDisplayPlugin",
"3D TV - Side by Side Stereo",
"3D TV - Interleaved",
"Keyboard/Mouse"
};
if (DEVICE_BLACKLIST.contains(deviceName)) {
return;
}
const QString ACTION_NAME = "connected_device";
QJsonObject actionDetails;
const QString TYPE_OF_DEVICE = "type_of_device";
@ -148,12 +162,34 @@ void UserActivityLogger::loadedScript(QString scriptName) {
}
void UserActivityLogger::wentTo(QString destinationType, QString destinationName) {
void UserActivityLogger::wentTo(AddressManager::LookupTrigger lookupTrigger, QString destinationType, QString destinationName) {
// Only accept these types of triggers. Other triggers are usually used internally in AddressManager.
QString trigger;
switch (lookupTrigger) {
case AddressManager::UserInput:
trigger = "UserInput";
break;
case AddressManager::Back:
trigger = "Back";
break;
case AddressManager::Forward:
trigger = "Forward";
break;
case AddressManager::StartupFromSettings:
trigger = "StartupFromSettings";
break;
default:
return;
}
const QString ACTION_NAME = "went_to";
QJsonObject actionDetails;
const QString TRIGGER_TYPE_KEY = "trigger";
const QString DESTINATION_TYPE_KEY = "destination_type";
const QString DESTINATION_NAME_KEY = "detination_name";
actionDetails.insert(TRIGGER_TYPE_KEY, trigger);
actionDetails.insert(DESTINATION_TYPE_KEY, destinationType);
actionDetails.insert(DESTINATION_NAME_KEY, destinationName);

View file

@ -20,6 +20,7 @@
#include <QNetworkReply>
#include <SettingHandle.h>
#include "AddressManager.h"
class UserActivityLogger : public QObject {
Q_OBJECT
@ -42,7 +43,7 @@ public slots:
void changedDomain(QString domainURL);
void connectedDevice(QString typeOfDevice, QString deviceName);
void loadedScript(QString scriptName);
void wentTo(QString destinationType, QString destinationName);
void wentTo(AddressManager::LookupTrigger trigger, QString destinationType, QString destinationName);
private slots:
void requestError(QNetworkReply& errorReply);

View file

@ -0,0 +1,31 @@
//
// UserActivityLoggerScriptingInterface.h
// libraries/networking/src
//
// Created by Ryan Huffman on 6/06/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "UserActivityLoggerScriptingInterface.h"
#include "UserActivityLogger.h"
void UserActivityLoggerScriptingInterface::enabledEdit() {
logAction("enabled_edit");
}
void UserActivityLoggerScriptingInterface::openedMarketplace() {
logAction("opened_marketplace");
}
void UserActivityLoggerScriptingInterface::toggledAway(bool isAway) {
logAction("toggled_away", { { "is_away", isAway } });
}
void UserActivityLoggerScriptingInterface::logAction(QString action, QJsonObject details) {
QMetaObject::invokeMethod(&UserActivityLogger::getInstance(), "logAction",
Q_ARG(QString, action),
Q_ARG(QJsonObject, details));
}

View file

@ -0,0 +1,31 @@
//
// UserActivityLoggerScriptingInterface.h
// libraries/networking/src
//
// Created by Ryan Huffman on 6/06/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_UserActivityLoggerScriptingInterface_h
#define hifi_UserActivityLoggerScriptingInterface_h
#include <QObject>
#include <QJsonObject>
#include <DependencyManager.h>
class UserActivityLoggerScriptingInterface : public QObject, public Dependency {
Q_OBJECT
public:
Q_INVOKABLE void enabledEdit();
Q_INVOKABLE void openedMarketplace();
Q_INVOKABLE void toggledAway(bool isAway);
private:
void logAction(QString action, QJsonObject details = {});
};
#endif // hifi_UserActivityLoggerScriptingInterface_h

View file

@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityAdd:
case PacketType::EntityEdit:
case PacketType::EntityData:
return VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS;
return VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH;
case PacketType::AvatarIdentity:
case PacketType::AvatarData:
case PacketType::BulkAvatarData:

View file

@ -180,6 +180,7 @@ const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57;
const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58;
const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59;
const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60;
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61;
enum class AvatarMixerPacketVersion : PacketVersion {
TranslationSupport = 17,

View file

@ -159,6 +159,11 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const {
}
assert(entityTreeIsLocked());
if (_entity->getShapeType() == SHAPE_TYPE_STATIC_MESH
|| (_body && _body->getCollisionShape()->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE)) {
return MOTION_TYPE_STATIC;
}
if (_entity->getDynamic()) {
if (!_entity->getParentID().isNull()) {
// if something would have been dynamic but is a child of something else, force it to be kinematic, instead.

View file

@ -203,35 +203,37 @@ void ObjectMotionState::handleEasyChanges(uint32_t& flags) {
}
}
if (flags & Simulation::DIRTY_LINEAR_VELOCITY) {
btVector3 newLinearVelocity = glmToBullet(getObjectLinearVelocity());
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
float delta = (newLinearVelocity - _body->getLinearVelocity()).length();
if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
if (_body->getCollisionShape()->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE) {
if (flags & Simulation::DIRTY_LINEAR_VELOCITY) {
btVector3 newLinearVelocity = glmToBullet(getObjectLinearVelocity());
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
float delta = (newLinearVelocity - _body->getLinearVelocity()).length();
if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
}
}
}
_body->setLinearVelocity(newLinearVelocity);
_body->setLinearVelocity(newLinearVelocity);
btVector3 newGravity = glmToBullet(getObjectGravity());
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
float delta = (newGravity - _body->getGravity()).length();
if (delta > ACTIVATION_GRAVITY_DELTA ||
(delta > 0.0f && _body->getGravity().length2() == 0.0f)) {
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
btVector3 newGravity = glmToBullet(getObjectGravity());
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
float delta = (newGravity - _body->getGravity()).length();
if (delta > ACTIVATION_GRAVITY_DELTA ||
(delta > 0.0f && _body->getGravity().length2() == 0.0f)) {
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
}
}
_body->setGravity(newGravity);
}
_body->setGravity(newGravity);
}
if (flags & Simulation::DIRTY_ANGULAR_VELOCITY) {
btVector3 newAngularVelocity = glmToBullet(getObjectAngularVelocity());
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
float delta = (newAngularVelocity - _body->getAngularVelocity()).length();
if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
if (flags & Simulation::DIRTY_ANGULAR_VELOCITY) {
btVector3 newAngularVelocity = glmToBullet(getObjectAngularVelocity());
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
float delta = (newAngularVelocity - _body->getAngularVelocity()).length();
if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
}
}
_body->setAngularVelocity(newAngularVelocity);
}
_body->setAngularVelocity(newAngularVelocity);
}
if (flags & Simulation::DIRTY_MATERIAL) {

View file

@ -217,7 +217,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
} else if (entity->isReadyToComputeShape()) {
ShapeInfo shapeInfo;
entity->computeShapeInfo(shapeInfo);
int numPoints = shapeInfo.getMaxNumPoints();
int numPoints = shapeInfo.getLargestSubshapePointCount();
if (numPoints > MAX_HULL_POINTS) {
qWarning() << "convex hull with" << numPoints
<< "points for entity" << entity->getName()
@ -231,7 +231,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
result.push_back(motionState);
entityItr = _entitiesToAddToPhysics.erase(entityItr);
} else {
//qDebug() << "Warning! Failed to generate new shape for entity." << entity->getName();
//qWarning() << "Failed to generate new shape for entity." << entity->getName();
++entityItr;
}
} else {

View file

@ -67,7 +67,8 @@ static const btVector3 _unitSphereDirections[NUM_UNIT_SPHERE_DIRECTIONS] = {
};
btConvexHullShape* ShapeFactory::createConvexHull(const QVector<glm::vec3>& points) {
// util method
btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) {
assert(points.size() > 0);
btConvexHullShape* hull = new btConvexHullShape();
@ -158,6 +159,84 @@ btConvexHullShape* ShapeFactory::createConvexHull(const QVector<glm::vec3>& poin
return hull;
}
// util method
btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) {
assert(info.getType() == SHAPE_TYPE_STATIC_MESH); // should only get here for mesh shapes
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
assert(pointCollection.size() == 1); // should only have one mesh
const ShapeInfo::PointList& pointList = pointCollection[0];
assert(pointList.size() > 2); // should have at least one triangle's worth of points
const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
assert(triangleIndices.size() > 2); // should have at least one triangle's worth of indices
// allocate mesh buffers
btIndexedMesh mesh;
int32_t numIndices = triangleIndices.size();
const int32_t VERTICES_PER_TRIANGLE = 3;
mesh.m_numTriangles = numIndices / VERTICES_PER_TRIANGLE;
if (numIndices < INT16_MAX) {
// small number of points so we can use 16-bit indices
mesh.m_triangleIndexBase = new unsigned char[sizeof(int16_t) * (size_t)numIndices];
mesh.m_indexType = PHY_SHORT;
mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int16_t);
} else {
mesh.m_triangleIndexBase = new unsigned char[sizeof(int32_t) * (size_t)numIndices];
mesh.m_indexType = PHY_INTEGER;
mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int32_t);
}
mesh.m_numVertices = pointList.size();
mesh.m_vertexBase = new unsigned char[VERTICES_PER_TRIANGLE * sizeof(btScalar) * (size_t)mesh.m_numVertices];
mesh.m_vertexStride = VERTICES_PER_TRIANGLE * sizeof(btScalar);
mesh.m_vertexType = PHY_FLOAT;
// copy data into buffers
btScalar* vertexData = static_cast<btScalar*>((void*)(mesh.m_vertexBase));
for (int32_t i = 0; i < mesh.m_numVertices; ++i) {
int32_t j = i * VERTICES_PER_TRIANGLE;
const glm::vec3& point = pointList[i];
vertexData[j] = point.x;
vertexData[j + 1] = point.y;
vertexData[j + 2] = point.z;
}
if (numIndices < INT16_MAX) {
int16_t* indices = static_cast<int16_t*>((void*)(mesh.m_triangleIndexBase));
for (int32_t i = 0; i < numIndices; ++i) {
indices[i] = triangleIndices[i];
}
} else {
int32_t* indices = static_cast<int32_t*>((void*)(mesh.m_triangleIndexBase));
for (int32_t i = 0; i < numIndices; ++i) {
indices[i] = triangleIndices[i];
}
}
// store buffers in a new dataArray and return the pointer
// (external StaticMeshShape will own all of the data that was allocated here)
btTriangleIndexVertexArray* dataArray = new btTriangleIndexVertexArray;
dataArray->addIndexedMesh(mesh, mesh.m_indexType);
return dataArray;
}
// util method
void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) {
assert(dataArray);
IndexedMeshArray& meshes = dataArray->getIndexedMeshArray();
for (int32_t i = 0; i < meshes.size(); ++i) {
btIndexedMesh mesh = meshes[i];
mesh.m_numTriangles = 0;
delete [] mesh.m_triangleIndexBase;
mesh.m_triangleIndexBase = nullptr;
mesh.m_numVertices = 0;
delete [] mesh.m_vertexBase;
mesh.m_vertexBase = nullptr;
}
meshes.clear();
delete dataArray;
}
btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
btCollisionShape* shape = NULL;
int type = info.getType();
@ -179,15 +258,15 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
}
break;
case SHAPE_TYPE_COMPOUND: {
const QVector<QVector<glm::vec3>>& points = info.getPoints();
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
uint32_t numSubShapes = info.getNumSubShapes();
if (numSubShapes == 1) {
shape = createConvexHull(info.getPoints()[0]);
shape = createConvexHull(pointCollection[0]);
} else {
auto compound = new btCompoundShape();
btTransform trans;
trans.setIdentity();
foreach (QVector<glm::vec3> hullPoints, points) {
foreach (const ShapeInfo::PointList& hullPoints, pointCollection) {
btConvexHullShape* hull = createConvexHull(hullPoints);
compound->addChildShape (trans, hull);
}
@ -195,6 +274,11 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
}
}
break;
case SHAPE_TYPE_STATIC_MESH: {
btTriangleIndexVertexArray* dataArray = createStaticMeshArray(info);
shape = new StaticMeshShape(dataArray);
}
break;
}
if (shape) {
if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) {
@ -228,3 +312,14 @@ void ShapeFactory::deleteShape(btCollisionShape* shape) {
}
delete shape;
}
// the dataArray must be created before we create the StaticMeshShape
ShapeFactory::StaticMeshShape::StaticMeshShape(btTriangleIndexVertexArray* dataArray)
: btBvhTriangleMeshShape(dataArray, true), _dataArray(dataArray) {
assert(dataArray);
}
ShapeFactory::StaticMeshShape::~StaticMeshShape() {
deleteStaticMeshArray(_dataArray);
_dataArray = nullptr;
}

Some files were not shown because too many files have changed in this diff Show more