Merge branch 'master' of http://github.com/highfidelity/hifi into taa

This commit is contained in:
Olivier Prat 2018-03-12 14:22:57 -04:00
commit aad571f6bd
98 changed files with 934 additions and 645 deletions

View file

@ -33,8 +33,6 @@ Item {
width: parent.width
height: parent.height
}
FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; }
Timer {
id: updateList

View file

@ -109,9 +109,9 @@ CheckBox {
contentItem: Text {
id: root
FontLoader { id: ralewaySemiBold; source: "qrc:/fonts/Raleway-SemiBold.ttf"; }
font.pixelSize: hifi.fontSizes.inputLabel
font.family: ralewaySemiBold.name
font.family: "Raleway"
font.weight: Font.DemiBold
text: checkBox.text
color: checkBox.color
x: 2

View file

@ -125,8 +125,7 @@ Rectangle {
TextInput {
id: mirrorText
visible: showMirrorText
FontLoader { id: font; source: "qrc:/fonts/FiraSans-Regular.ttf"; }
font.family: font.name
font.family: "Fira Sans"
font.pixelSize: 20
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
@ -165,8 +164,6 @@ Rectangle {
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
FontLoader { id: hiFiGlyphs; source: "qrc:/fonts/hifi-glyphs.ttf"; }
Column {
id: columnAlpha
width: keyboardWidth
@ -250,7 +247,7 @@ Rectangle {
Key { width: 43; glyph: ","; }
Key { width: 43; glyph: "."; }
Key {
fontFamily: hiFiGlyphs.name;
fontFamily: "hifi-glyphs";
fontPixelSize: 48;
letterAnchors.topMargin: -4;
verticalAlignment: Text.AlignVCenter;
@ -343,7 +340,7 @@ Rectangle {
Key { width: 43; glyph: ","; }
Key { width: 43; glyph: "."; }
Key {
fontFamily: hiFiGlyphs.name;
fontFamily: "hifi-glyphs";
fontPixelSize: 48;
letterAnchors.topMargin: -4;
verticalAlignment: Text.AlignVCenter;

View file

@ -25,8 +25,7 @@ SpinBox {
property color colorLabelInside: hifi.colors.white
property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0)
FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; }
font.family: firaSansSemiBold.name
font.family: "Fira Sans SemiBold"
font.pixelSize: hifi.fontSizes.textFieldInput
height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control.

View file

@ -16,9 +16,9 @@ import "../styles-uit"
TextEdit {
property real size: 32
FontLoader { id: ralewaySemiBold; source: "qrc:/fonts/Raleway-SemiBold.ttf"; }
font.family: ralewaySemiBold.name
font.family: "Raleway"
font.weight: Font.DemiBold
font.pointSize: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft

View file

@ -34,9 +34,7 @@ TextField {
placeholderText: textField.placeholderText
FontLoader { id: firaSansRegular; source: "qrc:/fonts/FiraSans-Regular.ttf"; }
FontLoader { id: hifiGlyphs; source: "qrc:/fonts/hifi-glyphs.ttf"; }
font.family: firaSansRegular.name
font.family: "Fira Sans"
font.pixelSize: hifi.fontSizes.textFieldInput
height: implicitHeight + 3 // Make surrounding box higher so that highlight is vertically centered.
property alias textFieldLabel: textFieldLabel

View file

@ -4,13 +4,12 @@ import QtQuick.Controls.Styles 1.3
Text {
id: root
FontLoader { id: iconFont; source: "qrc:/fonts/fontawesome-webfont.ttf"; }
property int size: 32
width: size
height: size
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.family: iconFont.name
font.family: "FontAwesome"
}

View file

@ -532,9 +532,6 @@ ModalWindow {
itemDelegate: Item {
clip: true
FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; }
FontLoader { id: firaSansRegular; source: "qrc:/fonts/FiraSans-Regular.ttf"; }
FiraSansSemiBold {
text: getText();
elide: styleData.elideMode
@ -548,7 +545,7 @@ ModalWindow {
size: hifi.fontSizes.tableText
color: hifi.colors.baseGrayHighlight
font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir)
? firaSansSemiBold.name : firaSansRegular.name
? "Fira Sans SemiBold" : "Fira Sans"
function getText() {
if (styleData.row === -1) {

View file

@ -496,9 +496,6 @@ TabletModalWindow {
itemDelegate: Item {
clip: true
//FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; }
//FontLoader { id: firaSansRegular; source: "qrc:/fonts/FiraSans-Regular.ttf"; }
FiraSansSemiBold {
text: getText();
elide: styleData.elideMode
@ -512,7 +509,7 @@ TabletModalWindow {
size: hifi.fontSizes.tableText
color: hifi.colors.baseGrayHighlight
//font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir)
//? firaSansSemiBold.name : firaSansRegular.name
//? "Fira Sans SemiBold" : "Fira Sans"
function getText() {
if (styleData.row === -1) {

View file

@ -345,9 +345,6 @@ Item {
itemDelegate: Item {
clip: true
FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; }
FontLoader { id: firaSansRegular; source: "qrc:/fonts/FiraSans-Regular.ttf"; }
FiraSansSemiBold {
text: styleData.value
elide: styleData.elideMode
@ -361,7 +358,7 @@ Item {
size: hifi.fontSizes.tableText
color: hifi.colors.baseGrayHighlight
font.family: (styleData.row !== -1 && assetTableView.model.get(styleData.row).fileIsDir)
? firaSansSemiBold.name : firaSansRegular.name
? "Fira Sans SemiBold" : "Fira Sans"
}
}

View file

@ -660,8 +660,7 @@ Windows.ScrollingWindow {
text: styleData.value
FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; }
font.family: firaSansSemiBold.name
font.family: "Fira Sans SemiBold"
font.pixelSize: hifi.fontSizes.textFieldInput
height: hifi.dimensions.tableRowHeight

View file

@ -25,8 +25,6 @@ Item {
property int dialogHeight;
property int comboOptionTextSize: 16;
property int comboBodyTextSize: 16;
FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; }
FontLoader { id: ralewaySemiBold; source: "qrc:/fonts/Raleway-SemiBold.ttf"; }
visible: false;
id: combo;
anchors.fill: parent;

View file

@ -24,8 +24,6 @@ Item {
property real headerTextMargin: -5
property real headerGlyphMargin: -15
property bool isDesktop: false
FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; }
FontLoader { id: ralewaySemiBold; source: "qrc:/fonts/Raleway-SemiBold.ttf"; }
visible: false
id: letterbox
anchors.fill: parent
@ -78,7 +76,8 @@ Item {
// Text Size
font.pixelSize: headerTextPixelSize
// Style
font.family: ralewaySemiBold.name
font.family: "Raleway"
font.weight: Font.DemiBold
color: hifi.colors.darkGray
horizontalAlignment: Text.AlignHLeft
verticalAlignment: Text.AlignVCenter
@ -101,7 +100,7 @@ Item {
horizontalAlignment: Text.AlignHLeft
// Style
font.pixelSize: popupTextPixelSize
font.family: ralewayRegular.name
font.family: "Raleway"
color: hifi.colors.darkGray
wrapMode: Text.WordWrap
textFormat: Text.StyledText

View file

@ -23,8 +23,6 @@ Item {
property real popupTextPixelSize: 16
property real headerTextMargin: -5
property real headerGlyphMargin: -15
FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; }
FontLoader { id: ralewaySemiBold; source: "qrc:/fonts/Raleway-SemiBold.ttf"; }
visible: false
id: letterbox
anchors.fill: parent
@ -82,7 +80,8 @@ Item {
// Text Size
font.pixelSize: headerTextPixelSize
// Style
font.family: ralewaySemiBold.name
font.family: "Raleway"
font.weight: Font.DemiBold
color: hifi.colors.darkGray
horizontalAlignment: Text.AlignHLeft
verticalAlignment: Text.AlignVCenter
@ -127,7 +126,7 @@ Item {
horizontalAlignment: Text.AlignHLeft
// Style
font.pixelSize: popupTextPixelSize
font.family: ralewayRegular.name
font.family: "Raleway"
color: hifi.colors.darkGray
wrapMode: Text.WordWrap
textFormat: Text.StyledText

View file

@ -177,8 +177,7 @@ Item {
anchors.right: parent.right
anchors.rightMargin: editGlyph.width + editGlyph.anchors.rightMargin
// Style
FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; }
font.family: firaSansSemiBold.name
font.family: "Fira Sans SemiBold"
font.pixelSize: displayNameTextPixelSize
selectionColor: hifi.colors.blueAccent
selectedTextColor: "black"

View file

@ -908,7 +908,6 @@ Rectangle {
anchors.horizontalCenter: parent.horizontalCenter;
}
FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; }
Text {
id: connectionHelpText;
// Anchors
@ -923,7 +922,7 @@ Rectangle {
horizontalAlignment: Text.AlignHLeft
// Style
font.pixelSize: 18;
font.family: ralewayRegular.name
font.family: "Raleway"
color: hifi.colors.darkGray
wrapMode: Text.WordWrap
textFormat: Text.StyledText;

View file

@ -128,10 +128,10 @@ Rectangle {
AudioControls.CheckBox {
id: stereoMic
spacing: muteMic.spacing;
text: qsTr("use stereo for stereo devices");
checked: false;
text: qsTr("Enable stereo input");
checked: AudioScriptingInterface.isStereoInput();
onClicked: {
var success = Audio.setIsStereoInput(checked);
var success = AudioScriptingInterface.setStereoInput(checked);
if (!success) {
checked = !checked;
}
@ -219,7 +219,7 @@ Rectangle {
onPressed: {
if (!checked) {
stereoMic.checked = false;
Audio.setIsStereoInput(false); // the next selected audio device might not support stereo
AudioScriptingInterface.setStereoInput(false); // the next selected audio device might not support stereo
AudioScriptingInterface.setInputDevice(info, bar.currentIndex === 1);
}
}

View file

@ -147,8 +147,7 @@ Rectangle {
} else if (root.itemHref.indexOf('.json') > -1) {
root.itemType = "entity"; // "wearable" type handled later
} else {
console.log("WARNING - Item type is UNKNOWN!");
root.itemType = "entity";
root.itemType = "unknown";
}
}

View file

@ -141,10 +141,9 @@ Item {
}
}
FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; }
TextMetrics {
id: textMetrics;
font.family: ralewayRegular.name
font.family: "Raleway"
text: usernameText.text;
}

View file

@ -174,11 +174,12 @@ Rectangle {
WalletChoice {
id: walletChoice;
proceedFunction: function (isReset) {
console.log(isReset ? "Reset wallet." : "Trying again with new wallet.");
console.log("WalletChoice", isReset ? "Reset wallet." : "Trying again with new wallet.");
Commerce.setSoftReset();
if (isReset) {
walletResetSetup();
} else {
Commerce.clearWallet();
var msg = { referrer: walletChoice.referrer }
followReferrer(msg);
}

View file

@ -997,14 +997,13 @@ Item {
anchors.right: parent.right;
anchors.rightMargin: 20;
height: 95;
FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; }
TextArea {
id: optionalMessage;
property int maximumLength: 72;
property string previousText: text;
placeholderText: "<i>Optional Public Message (" + maximumLength + " character limit)</i>";
font.family: firaSansSemiBold.name;
font.family: "Fira Sans SemiBold";
font.pixelSize: 20;
// Anchors
anchors.fill: parent;

View file

@ -659,8 +659,7 @@ Rectangle {
text: styleData.value
FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; }
font.family: firaSansSemiBold.name
font.family: "Fira Sans SemiBold"
font.pixelSize: hifi.fontSizes.textFieldInput
height: hifi.dimensions.tableRowHeight

View file

@ -478,9 +478,6 @@ Rectangle {
itemDelegate: Item {
clip: true
//FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; }
//FontLoader { id: firaSansRegular; source: "qrc:/fonts/FiraSans-Regular.ttf"; }
FiraSansSemiBold {
text: getText();
elide: styleData.elideMode
@ -494,7 +491,7 @@ Rectangle {
size: hifi.fontSizes.tableText
color: hifi.colors.baseGrayHighlight
//font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir)
//? firaSansSemiBold.name : firaSansRegular.name
//? "Fira Sans SemiBold" : "Fira Sans"
function getText() {
if (styleData.row === -1) {

View file

@ -14,10 +14,9 @@ import QtQuick.Controls.Styles 1.4
Text {
id: root
FontLoader { id: anonymousProRegular; source: "qrc:/fonts/AnonymousPro-Regular.ttf"; }
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.family: anonymousProRegular.name
font.family: "Anonymous Pro"
}

View file

@ -14,10 +14,9 @@ import QtQuick.Controls.Styles 1.4
Text {
id: root
FontLoader { id: firaSansRegular; source: "qrc:/fonts/FiraSans-Regular.ttf"; }
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.family: firaSansRegular.name
font.family: "Fira Sans"
}

View file

@ -14,10 +14,9 @@ import QtQuick.Controls.Styles 1.4
Text {
id: root
FontLoader { id: firaSansSemiBold; source: "qrc:/fonts/FiraSans-SemiBold.ttf"; }
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.family: firaSansSemiBold.name
font.family: "Fira Sans SemiBold"
}

View file

@ -12,12 +12,11 @@ import QtQuick 2.5
Text {
id: root
FontLoader { id: hiFiGlyphs; source: "qrc:/fonts/hifi-glyphs.ttf"; }
property int size: 32
font.pixelSize: size
width: size
height: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.family: hiFiGlyphs.name
font.family: "hifi-glyphs"
}

View file

@ -14,11 +14,10 @@ import QtQuick.Controls.Styles 1.4
Text {
id: root
FontLoader { id: ralewayBold; source: "qrc:/fonts/Raleway-Bold.ttf"; }
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.family: ralewayBold.name
font.bold: true // Font seems to need this in order to display bold.
font.family: "Raleway"
font.bold: true
}

View file

@ -14,10 +14,9 @@ import QtQuick.Controls.Styles 1.4
Text {
id: root
FontLoader { id: ralewayLight; source: "qrc:/fonts/Raleway-Light.ttf"; }
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.family: ralewayLight.name
font.family: "Raleway Light"
}

View file

@ -14,10 +14,9 @@ import QtQuick.Controls.Styles 1.4
Text {
id: root
FontLoader { id: ralewayRegular; source: "qrc:/fonts/Raleway-Regular.ttf"; }
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.family: ralewayRegular.name
font.family: "Raleway"
}

View file

@ -14,10 +14,10 @@ import QtQuick.Controls.Styles 1.4
Text {
id: root
FontLoader { id: ralewaySemiBold; source: "qrc:/fonts/Raleway-SemiBold.ttf"; }
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.family: ralewaySemiBold.name
font.family: "Raleway"
font.weight: Font.DemiBold
}

View file

@ -981,6 +981,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
qInstallMessageHandler(messageHandler);
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/fontawesome-webfont.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/hifi-glyphs.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/AnonymousPro-Regular.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/FiraSans-Regular.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/FiraSans-SemiBold.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Light.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Regular.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Bold.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-SemiBold.ttf");
_window->setWindowTitle("High Fidelity Interface");
Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us
@ -6637,17 +6646,17 @@ void Application::addAssetToWorld(QString path, QString zipFile, bool isZip, boo
addAssetToWorldInfo(filename, "Adding " + mapping.mid(1) + " to the Asset Server.");
addAssetToWorldWithNewMapping(path, mapping, 0);
addAssetToWorldWithNewMapping(path, mapping, 0, isZip, isBlocks);
}
void Application::addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy) {
void Application::addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy, bool isZip, bool isBlocks) {
auto request = DependencyManager::get<AssetClient>()->createGetMappingRequest(mapping);
QObject::connect(request, &GetMappingRequest::finished, this, [=](GetMappingRequest* request) mutable {
const int MAX_COPY_COUNT = 100; // Limit number of duplicate assets; recursion guard.
auto result = request->getError();
if (result == GetMappingRequest::NotFound) {
addAssetToWorldUpload(filePath, mapping);
addAssetToWorldUpload(filePath, mapping, isZip, isBlocks);
} else if (result != GetMappingRequest::NoError) {
QString errorInfo = "Could not map asset name: "
+ mapping.left(mapping.length() - QString::number(copy).length() - 1);
@ -6659,7 +6668,7 @@ void Application::addAssetToWorldWithNewMapping(QString filePath, QString mappin
}
copy++;
mapping = mapping.insert(mapping.lastIndexOf("."), "-" + QString::number(copy));
addAssetToWorldWithNewMapping(filePath, mapping, copy);
addAssetToWorldWithNewMapping(filePath, mapping, copy, isZip, isBlocks);
} else {
QString errorInfo = "Too many copies of asset name: "
+ mapping.left(mapping.length() - QString::number(copy).length() - 1);
@ -6672,7 +6681,7 @@ void Application::addAssetToWorldWithNewMapping(QString filePath, QString mappin
request->start();
}
void Application::addAssetToWorldUpload(QString filePath, QString mapping) {
void Application::addAssetToWorldUpload(QString filePath, QString mapping, bool isZip, bool isBlocks) {
qInfo(interfaceapp) << "Uploading" << filePath << "to Asset Server as" << mapping;
auto upload = DependencyManager::get<AssetClient>()->createUpload(filePath);
QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable {
@ -6681,7 +6690,7 @@ void Application::addAssetToWorldUpload(QString filePath, QString mapping) {
qWarning(interfaceapp) << "Error downloading model: " + errorInfo;
addAssetToWorldError(filenameFromPath(filePath), errorInfo);
} else {
addAssetToWorldSetMapping(filePath, mapping, hash);
addAssetToWorldSetMapping(filePath, mapping, hash, isZip, isBlocks);
}
// Remove temporary directory created by Clara.io market place download.
@ -6698,7 +6707,7 @@ void Application::addAssetToWorldUpload(QString filePath, QString mapping) {
upload->start();
}
void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, QString hash) {
void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, QString hash, bool isZip, bool isBlocks) {
auto request = DependencyManager::get<AssetClient>()->createSetMappingRequest(mapping, hash);
connect(request, &SetMappingRequest::finished, this, [=](SetMappingRequest* request) mutable {
if (request->getError() != SetMappingRequest::NoError) {
@ -6706,9 +6715,10 @@ void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, Q
qWarning(interfaceapp) << "Error downloading model: " + errorInfo;
addAssetToWorldError(filenameFromPath(filePath), errorInfo);
} else {
// to prevent files that aren't models from being loaded into world automatically
if (filePath.toLower().endsWith(OBJ_EXTENSION) || filePath.toLower().endsWith(FBX_EXTENSION) ||
filePath.toLower().endsWith(JPG_EXTENSION) || filePath.toLower().endsWith(PNG_EXTENSION)) {
// to prevent files that aren't models or texture files from being loaded into world automatically
if ((filePath.toLower().endsWith(OBJ_EXTENSION) || filePath.toLower().endsWith(FBX_EXTENSION)) ||
((filePath.toLower().endsWith(JPG_EXTENSION) || filePath.toLower().endsWith(PNG_EXTENSION)) &&
((!isBlocks) && (!isZip)))) {
addAssetToWorldAddEntity(filePath, mapping);
} else {
qCDebug(interfaceapp) << "Zipped contents are not supported entity files";

View file

@ -321,11 +321,11 @@ public slots:
// FIXME: Move addAssetToWorld* methods to own class?
void addAssetToWorldFromURL(QString url);
void addAssetToWorldFromURLRequestFinished();
void addAssetToWorld(QString filePath, QString zipFile, bool isZip, bool isBlocks);
void addAssetToWorld(QString filePath, QString zipFile, bool isZip = false, bool isBlocks = false);
void addAssetToWorldUnzipFailure(QString filePath);
void addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy);
void addAssetToWorldUpload(QString filePath, QString mapping);
void addAssetToWorldSetMapping(QString filePath, QString mapping, QString hash);
void addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy, bool isZip = false, bool isBlocks = false);
void addAssetToWorldUpload(QString filePath, QString mapping, bool isZip = false, bool isBlocks = false);
void addAssetToWorldSetMapping(QString filePath, QString mapping, QString hash, bool isZip = false, bool isBlocks = false);
void addAssetToWorldAddEntity(QString filePath, QString mapping);
void handleUnzip(QString sourceFile, QStringList destinationFile, bool autoAdd, bool isZip, bool isBlocks);

View file

@ -45,6 +45,9 @@
#include "LocationBookmarks.h"
#include "DeferredLightingEffect.h"
#include "AmbientOcclusionEffect.h"
#include "RenderShadowTask.h"
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
#include "SpeechRecognizer.h"
#endif
@ -361,18 +364,6 @@ Menu::Menu() {
// Developer menu ----------------------------------
MenuWrapper* developerMenu = addMenu("Developer", "Developer");
// Developer > Graphics
MenuWrapper* graphicsOptionsMenu = developerMenu->addMenu("Render");
action = addCheckableActionToQMenuAndActionHash(graphicsOptionsMenu, MenuOption::Shadows, 0, true);
connect(action, &QAction::triggered, [action] {
DependencyManager::get<DeferredLightingEffect>()->setShadowMapEnabled(action->isChecked());
});
action = addCheckableActionToQMenuAndActionHash(graphicsOptionsMenu, MenuOption::AmbientOcclusion, 0, false);
connect(action, &QAction::triggered, [action] {
DependencyManager::get<DeferredLightingEffect>()->setAmbientOcclusionEnabled(action->isChecked());
});
// Developer > UI >>>
MenuWrapper* uiOptionsMenu = developerMenu->addMenu("UI");
action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::DesktopTabletToToolbar, 0,
@ -389,6 +380,36 @@ Menu::Menu() {
// Developer > Render >>>
MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render");
action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, true);
connect(action, &QAction::triggered, [action] {
auto renderConfig = qApp->getRenderEngine()->getConfiguration();
if (renderConfig) {
auto mainViewShadowTaskConfig = renderConfig->getConfig<RenderShadowTask>("RenderMainView.RenderShadowTask");
if (mainViewShadowTaskConfig) {
if (action->isChecked()) {
mainViewShadowTaskConfig->setPreset("Enabled");
} else {
mainViewShadowTaskConfig->setPreset("None");
}
}
}
});
action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion, 0, false);
connect(action, &QAction::triggered, [action] {
auto renderConfig = qApp->getRenderEngine()->getConfiguration();
if (renderConfig) {
auto mainViewAmbientOcclusionConfig = renderConfig->getConfig<AmbientOcclusionEffect>("RenderMainView.AmbientOcclusion");
if (mainViewAmbientOcclusionConfig) {
if (action->isChecked()) {
mainViewAmbientOcclusionConfig->setPreset("Enabled");
} else {
mainViewAmbientOcclusionConfig->setPreset("None");
}
}
}
});
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::WorldAxes);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DefaultSkybox, 0, true);

View file

@ -205,7 +205,7 @@ namespace MenuOption {
const QString DesktopTabletToToolbar = "Desktop Tablet Becomes Toolbar";
const QString HMDTabletToToolbar = "HMD Tablet Becomes Toolbar";
const QString Shadows = "Shadows";
const QString AmbientOcclusion = "AmbientOcclusion";
const QString AmbientOcclusion = "Ambient Occlusion";
}
#endif // hifi_Menu_h

View file

@ -1115,7 +1115,6 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) {
void MyAvatar::setEnableMeshVisible(bool isEnabled) {
_skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
_skeletonModel->setCanCastShadow(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
}
void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
@ -1468,7 +1467,6 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
int skeletonModelChangeCount = _skeletonModelChangeCount;
Avatar::setSkeletonModelURL(skeletonModelURL);
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
_skeletonModel->setCanCastShadow(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
_headBoneSet.clear();
_cauterizationNeedsUpdate = true;
@ -2043,8 +2041,8 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) {
_attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene(),
render::ItemKey::TAG_BITS_NONE, true);
_attachmentModels[i]->setCanCastShadow(shouldDrawHead, qApp->getMain3DScene(),
render::ItemKey::TAG_BITS_NONE, true);
_attachmentModels[i]->setCanCastShadow(shouldDrawHead, qApp->getMain3DScene(),
render::ItemKey::TAG_BITS_NONE, true);
}
}
}

View file

@ -80,9 +80,13 @@ void Ledger::signedSend(const QString& propertyName, const QByteArray& text, con
void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& requestParams) {
auto wallet = DependencyManager::get<Wallet>();
requestParams["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, requestParams);
QStringList cachedPublicKeys = wallet->listPublicKeys();
if (!cachedPublicKeys.isEmpty()) {
requestParams["public_keys"] = QJsonArray::fromStringList(cachedPublicKeys);
send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, requestParams);
} else {
qDebug(commerce) << "User attempted to call keysQuery, but cachedPublicKeys was empty!";
}
}
void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) {
@ -296,14 +300,18 @@ void Ledger::updateLocation(const QString& asset_id, const QString location, con
emit walletScriptingInterface->walletNotSetup();
qDebug(commerce) << "User attempted to update the location of a certificate, but their wallet wasn't ready. Status:" << walletStatus;
} else {
QStringList keys = wallet->listPublicKeys();
QString key = keys[0];
QJsonObject transaction;
transaction["certificate_id"] = asset_id;
transaction["place_name"] = location;
QJsonDocument transactionDoc{ transaction };
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
signedSend("transaction", transactionString, key, "location", "updateLocationSuccess", "updateLocationFailure", controlledFailure);
QStringList cachedPublicKeys = wallet->listPublicKeys();
if (!cachedPublicKeys.isEmpty()) {
QString key = cachedPublicKeys[0];
QJsonObject transaction;
transaction["certificate_id"] = asset_id;
transaction["place_name"] = location;
QJsonDocument transactionDoc{ transaction };
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
signedSend("transaction", transactionString, key, "location", "updateLocationSuccess", "updateLocationFailure", controlledFailure);
} else {
qDebug(commerce) << "User attempted to update the location of a certificate, but cachedPublicKeys was empty!";
}
}
}
@ -359,7 +367,12 @@ void Ledger::alreadyOwned(const QString& marketplaceId) {
auto wallet = DependencyManager::get<Wallet>();
QString endpoint = "already_owned";
QJsonObject request;
request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
request["marketplace_item_id"] = marketplaceId;
send(endpoint, "alreadyOwnedSuccess", "alreadyOwnedFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request);
QStringList cachedPublicKeys = wallet->listPublicKeys();
if (!cachedPublicKeys.isEmpty()) {
request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
request["marketplace_item_id"] = marketplaceId;
send(endpoint, "alreadyOwnedSuccess", "alreadyOwnedFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request);
} else {
qDebug(commerce) << "User attempted to use the alreadyOwned endpoint, but cachedPublicKeys was empty!";
}
}

View file

@ -138,6 +138,11 @@ void QmlCommerce::setSoftReset() {
wallet->setSoftReset();
}
void QmlCommerce::clearWallet() {
auto wallet = DependencyManager::get<Wallet>();
wallet->clear();
}
void QmlCommerce::setPassphrase(const QString& passphrase) {
auto wallet = DependencyManager::get<Wallet>();
wallet->setPassphrase(passphrase);

View file

@ -67,6 +67,7 @@ protected:
Q_INVOKABLE void setPassphrase(const QString& passphrase);
Q_INVOKABLE void changePassphrase(const QString& oldPassphrase, const QString& newPassphrase);
Q_INVOKABLE void setSoftReset();
Q_INVOKABLE void clearWallet();
Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false);
Q_INVOKABLE void balance();

View file

@ -343,19 +343,23 @@ Wallet::Wallet() {
auto accountManager = DependencyManager::get<AccountManager>();
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
getWalletStatus();
_publicKeys.clear();
if (_securityImage) {
delete _securityImage;
}
_securityImage = nullptr;
// tell the provider we got nothing
updateImageProvider();
_passphrase->clear();
clear();
});
}
void Wallet::clear() {
_publicKeys.clear();
if (_securityImage) {
delete _securityImage;
}
_securityImage = nullptr;
// tell the provider we got nothing
updateImageProvider();
_passphrase->clear();
}
Wallet::~Wallet() {
if (_securityImage) {
delete _securityImage;

View file

@ -49,8 +49,9 @@ public:
bool getPassphraseIsCached() { return !(_passphrase->isEmpty()); }
bool walletIsAuthenticatedWithPassphrase();
bool changePassphrase(const QString& newPassphrase);
void setSoftReset() { _isOverridingServer = true; }
void setSoftReset() { _isOverridingServer = true; }
bool wasSoftReset() { bool was = _isOverridingServer; _isOverridingServer = false; return was; }
void clear();
void getWalletStatus();
enum WalletStatus {

View file

@ -174,4 +174,12 @@ void PickScriptingInterface::registerMetaTypes(QScriptEngine* engine) {
engine->globalObject().setProperty("PickType", pickTypes);
qScriptRegisterMetaType(engine, pickTypesToScriptValue, pickTypesFromScriptValue);
}
}
unsigned int PickScriptingInterface::getPerFrameTimeBudget() const {
return DependencyManager::get<PickManager>()->getPerFrameTimeBudget();
}
void PickScriptingInterface::setPerFrameTimeBudget(unsigned int numUsecs) {
DependencyManager::get<PickManager>()->setPerFrameTimeBudget(numUsecs);
}

View file

@ -185,6 +185,14 @@ public:
*/
Q_INVOKABLE bool isMouse(unsigned int uid);
Q_PROPERTY(unsigned int perFrameTimeBudget READ getPerFrameTimeBudget WRITE setPerFrameTimeBudget)
/**jsdoc
* The max number of usec to spend per frame updating Pick results.
* @typedef {number} Picks.perFrameTimeBudget
*/
unsigned int getPerFrameTimeBudget() const;
void setPerFrameTimeBudget(unsigned int numUsecs);
public slots:
static constexpr unsigned int PICK_NOTHING() { return 0; }
static constexpr unsigned int PICK_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ENTITIES); }
@ -202,4 +210,4 @@ public slots:
static constexpr unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; }
};
#endif // hifi_PickScriptingInterface_h
#endif // hifi_PickScriptingInterface_h

View file

@ -259,7 +259,6 @@ void WindowScriptingInterface::browseAsync(const QString& title, const QString&
setPreviousBrowseLocation(QFileInfo(result).absolutePath());
}
emit browseChanged(result);
emit openFileChanged(result); // Deprecated signal; to be removed in due course.
});
}

View file

@ -179,7 +179,6 @@ public slots:
* Prompt the user to choose a file. Displays a non-modal dialog that navigates the directory tree. A
* {@link Window.browseChanged|browseChanged} signal is emitted when a file is chosen; no signal is emitted if the user
* cancels the dialog.
* @deprecated A deprecated {@link Window.openFileChanged|openFileChanged} signal is also emitted when a file is chosen.
* @function Window.browseAsync
* @param {string} title="" - The title to display at the top of the dialog.
* @param {string} directory="" - The initial directory to start browsing at.
@ -660,15 +659,6 @@ signals:
*/
void browseChanged(QString filename);
/**jsdoc
* Triggered when the user chooses a file in a {@link Window.browseAsync|browseAsync} dialog.
* @function Window.openFileChanged
* @deprecated This signal is being replaced with {@link Window.browseChanged|browseChanged} and will be removed.
* @param {string} filename - The path and name of the file the user chose in the dialog.
* @returns {Signal}
*/
void openFileChanged(QString filename);
/**jsdoc
* Triggered when the user OKs a {@link Window.promptAsync|promptAsync} dialog.
* @function Window.promptTextChanged

View file

@ -24,10 +24,6 @@
#include "SnapshotAnimated.h"
#include "UserActivityLogger.h"
#include "AmbientOcclusionEffect.h"
#include "AntialiasingEffect.h"
#include "RenderShadowTask.h"
void setupPreferences() {
auto preferences = DependencyManager::get<Preferences>();
auto nodeList = DependencyManager::get<NodeList>();
@ -295,30 +291,6 @@ void setupPreferences() {
}
#endif
{
static const QString RENDER("Graphics");
auto renderConfig = qApp->getRenderEngine()->getConfiguration();
if (renderConfig) {
auto mainViewAmbientOcclusionConfig = renderConfig->getConfig<AmbientOcclusionEffect>("RenderMainView.AmbientOcclusion");
if (mainViewAmbientOcclusionConfig) {
auto getter = [mainViewAmbientOcclusionConfig]()->QString { return mainViewAmbientOcclusionConfig->getPreset(); };
auto setter = [mainViewAmbientOcclusionConfig](QString preset) { mainViewAmbientOcclusionConfig->setPreset(preset); };
auto preference = new ComboBoxPreference(RENDER, "Ambient occlusion", getter, setter);
preference->setItems(mainViewAmbientOcclusionConfig->getPresetList());
preferences->addPreference(preference);
}
auto mainViewShadowConfig = renderConfig->getConfig<RenderShadowTask>("RenderMainView.RenderShadowTask");
if (mainViewShadowConfig) {
auto getter = [mainViewShadowConfig]()->QString { return mainViewShadowConfig->getPreset(); };
auto setter = [mainViewShadowConfig](QString preset) { mainViewShadowConfig->setPreset(preset); };
auto preference = new ComboBoxPreference(RENDER, "Shadows", getter, setter);
preference->setItems(mainViewShadowConfig->getPresetList());
preferences->addPreference(preference);
}
}
}
{
static const QString NETWORKING("Networking");

View file

@ -193,6 +193,7 @@ public slots:
bool isMuted() { return _muted; }
virtual bool setIsStereoInput(bool stereo) override;
virtual bool isStereoInput() override { return _isStereoInput; }
void setNoiseReduction(bool isNoiseGateEnabled);
bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; }

View file

@ -28,7 +28,7 @@ class AbstractAudioInterface : public QObject {
Q_OBJECT
public:
AbstractAudioInterface(QObject* parent = 0) : QObject(parent) {};
static void emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, bool isStereo,
const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale,
PacketType packetType, QString codecName = QString(""));
@ -40,8 +40,10 @@ public:
public slots:
virtual bool shouldLoopbackInjectors() { return false; }
virtual bool setIsStereoInput(bool stereo) = 0;
virtual bool isStereoInput() = 0;
};
Q_DECLARE_METATYPE(AbstractAudioInterface*)

View file

@ -33,6 +33,10 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
{
// SkeletonModels, and by extention Avatars, use Dual Quaternion skinning.
_useDualQuaternionSkinning = true;
// Avatars all cast shadow
_canCastShadow = true;
assert(_owningAvatar);
}

View file

@ -286,7 +286,7 @@ bool RenderableModelEntityItem::supportsDetailedRayIntersection() const {
}
bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
OctreeElementPointer& element, float& distance, BoxFace& face,
glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const {
auto model = getModel();
if (!model) {

View file

@ -68,7 +68,7 @@ public:
virtual bool supportsDetailedRayIntersection() const override;
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;

View file

@ -565,7 +565,7 @@ public:
#endif
bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element,
OctreeElementPointer& element,
float& distance, BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const
{

View file

@ -53,7 +53,7 @@ public:
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;

View file

@ -2984,4 +2984,4 @@ std::unordered_map<std::string, graphics::MultiMaterial> EntityItem::getMaterial
toReturn = _materials;
}
return toReturn;
}
}

View file

@ -159,7 +159,7 @@ public:
virtual bool supportsDetailedRayIntersection() const { return false; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const { return true; }

View file

@ -594,17 +594,15 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
QVariantMap& extraInfo, bool precisionPicking) {
keepSearching = true; // assume that we will continue searching after this.
EntityItemID result;
float distanceToElementCube = std::numeric_limits<float>::max();
float distanceToElementDetails = distance;
BoxFace localFace;
glm::vec3 localSurfaceNormal;
QVariantMap localExtraInfo;
// if the ray doesn't intersect with our cube, we can stop searching!
if (!_cube.findRayIntersection(origin, direction, distanceToElementCube, localFace, localSurfaceNormal)) {
// if the ray doesn't intersect with our cube OR the distance to element is less than current best distance
// we can stop searching!
bool hit = _cube.findRayIntersection(origin, direction, distanceToElementCube, localFace, localSurfaceNormal);
if (!hit || (!_cube.contains(origin) && distanceToElementCube > distance)) {
keepSearching = false; // no point in continuing to search
return result; // we did not intersect
}
@ -616,52 +614,46 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con
// if the distance to the element cube is not less than the current best distance, then it's not possible
// for any details inside the cube to be closer so we don't need to consider them.
if (_cube.contains(origin) || distanceToElementCube < distance) {
EntityItemID entityID = findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails,
face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly,
localExtraInfo, precisionPicking, distanceToElementCube);
if (!entityID.isNull()) {
if (distanceToElementDetails < distance) {
distance = distanceToElementDetails;
face = localFace;
surfaceNormal = localSurfaceNormal;
extraInfo = localExtraInfo;
result = entityID;
}
}
QVariantMap localExtraInfo;
float distanceToElementDetails = distance;
EntityItemID entityID = findDetailedRayIntersection(origin, direction, element, distanceToElementDetails,
face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly,
localExtraInfo, precisionPicking);
if (!entityID.isNull() && distanceToElementDetails < distance) {
distance = distanceToElementDetails;
face = localFace;
surfaceNormal = localSurfaceNormal;
extraInfo = localExtraInfo;
result = entityID;
}
return result;
}
EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching,
EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal,
const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIDsToDiscard,
bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking, float distanceToElementCube) {
bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) {
// only called if we do intersect our bounding cube, but find if we actually intersect with entities...
int entityNumber = 0;
EntityItemID entityID;
forEachEntity([&](EntityItemPointer entity) {
if ( (visibleOnly && !entity->isVisible()) || (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE))
|| (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID()))
|| (entityIDsToDiscard.size() > 0 && entityIDsToDiscard.contains(entity->getID())) ) {
return;
}
// use simple line-sphere for broadphase check
// (this is faster and more likely to cull results than the filter check below so we do it first)
bool success;
AABox entityBox = entity->getAABox(success);
if (!success) {
return;
}
if (!entityBox.rayHitsBoundingSphere(origin, direction)) {
return;
}
float localDistance;
BoxFace localFace;
glm::vec3 localSurfaceNormal;
QVariantMap localExtraInfo;
// if the ray doesn't intersect with our cube, we can stop searching!
if (!entityBox.findRayIntersection(origin, direction, localDistance, localFace, localSurfaceNormal)) {
// check RayPick filter settings
if ((visibleOnly && !entity->isVisible())
|| (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE))
|| (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID()))
|| (entityIDsToDiscard.size() > 0 && entityIDsToDiscard.contains(entity->getID())) ) {
return;
}
@ -682,14 +674,17 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori
// we can use the AABox's ray intersection by mapping our origin and direction into the entity frame
// and testing intersection there.
float localDistance;
BoxFace localFace;
glm::vec3 localSurfaceNormal;
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance,
localFace, localSurfaceNormal)) {
if (entityFrameBox.contains(entityFrameOrigin) || localDistance < distance) {
// now ask the entity if we actually intersect
if (entity->supportsDetailedRayIntersection()) {
if (entity->findDetailedRayIntersection(origin, direction, keepSearching, element, localDistance,
localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) {
QVariantMap localExtraInfo;
if (entity->findDetailedRayIntersection(origin, direction, element, localDistance,
localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) {
if (localDistance < distance) {
distance = localDistance;
face = localFace;

View file

@ -152,10 +152,10 @@ public:
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
QVariantMap& extraInfo, bool precisionPicking = false);
virtual EntityItemID findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
QVariantMap& extraInfo, bool precisionPicking, float distanceToElementCube);
QVariantMap& extraInfo, bool precisionPicking);
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const override;

View file

@ -298,7 +298,7 @@ void LightEntityItem::resetLightPropertiesChanged() {
}
bool LightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {

View file

@ -86,7 +86,7 @@ public:
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;

View file

@ -61,7 +61,7 @@ class LineEntityItem : public EntityItem {
// never have a ray intersection pick a LineEntityItem.
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo,
bool precisionPicking) const override { return false; }

View file

@ -94,7 +94,7 @@ class PolyLineEntityItem : public EntityItem {
// never have a ray intersection pick a PolyLineEntityItem.
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }

View file

@ -45,7 +45,7 @@ class PolyVoxEntityItem : public EntityItem {
// never have a ray intersection pick a PolyVoxEntityItem.
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }

View file

@ -228,7 +228,7 @@ bool ShapeEntityItem::supportsDetailedRayIntersection() const {
}
bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element,
OctreeElementPointer& element,
float& distance, BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
// determine the ray in the frame of the entity transformed from a unit sphere

View file

@ -92,7 +92,7 @@ public:
bool supportsDetailedRayIntersection() const override;
bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;

View file

@ -129,7 +129,7 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
}
bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
glm::vec3 dimensions = getScaledDimensions();

View file

@ -48,7 +48,7 @@ public:
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;

View file

@ -106,7 +106,7 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst
}
bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
glm::vec3 dimensions = getScaledDimensions();

View file

@ -47,7 +47,7 @@ public:
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;

View file

@ -296,7 +296,7 @@ void ZoneEntityItem::setCompoundShapeURL(const QString& url) {
}
bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {

View file

@ -105,7 +105,7 @@ public:
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;

View file

@ -46,10 +46,6 @@ AddressManager::AddressManager() :
}
QString AddressManager::protocolVersion() {
return protocolVersionsSignatureBase64();
}
bool AddressManager::isConnected() {
return DependencyManager::get<NodeList>()->getDomainHandler().isConnected();
}

View file

@ -71,15 +71,6 @@ class AddressManager : public QObject, public Dependency {
Q_PROPERTY(QString domainID READ getDomainID)
Q_PROPERTY(QString domainId READ getDomainID)
public:
/**jsdoc
* Get Interface's protocol version.
* @function location.protocolVersion
* @returns {string} A string uniquely identifying the version of the metaverse protocol that Interface is using.
* @deprecated This function is deprecated and will be removed. Use {@link Window.protocolSignature} instead.
*/
Q_INVOKABLE QString protocolVersion();
using PositionGetter = std::function<glm::vec3()>;
using OrientationGetter = std::function<glm::quat()>;

View file

@ -37,7 +37,7 @@ template<typename T>
class PickCacheOptimizer {
public:
void update(std::unordered_map<unsigned int, std::shared_ptr<PickQuery>>& picks, bool shouldPickHUD);
void update(std::unordered_map<uint32_t, std::shared_ptr<PickQuery>>& picks, uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD);
protected:
typedef std::unordered_map<T, std::unordered_map<PickCacheKey, PickResultPointer>> PickCache;
@ -67,66 +67,84 @@ void PickCacheOptimizer<T>::cacheResult(const bool intersects, const PickResultP
}
template<typename T>
void PickCacheOptimizer<T>::update(std::unordered_map<unsigned int, std::shared_ptr<PickQuery>>& picks, bool shouldPickHUD) {
void PickCacheOptimizer<T>::update(std::unordered_map<uint32_t, std::shared_ptr<PickQuery>>& picks,
uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD) {
PickCache results;
for (const auto& pickPair : picks) {
std::shared_ptr<Pick<T>> pick = std::static_pointer_cast<Pick<T>>(pickPair.second);
const uint32_t INVALID_PICK_ID = 0;
auto itr = picks.begin();
if (nextToUpdate != INVALID_PICK_ID) {
itr = picks.find(nextToUpdate);
if (itr == picks.end()) {
itr = picks.begin();
}
}
uint32_t numUpdates = 0;
while(numUpdates < picks.size()) {
std::shared_ptr<Pick<T>> pick = std::static_pointer_cast<Pick<T>>(itr->second);
T mathematicalPick = pick->getMathematicalPick();
PickResultPointer res = pick->getDefaultResult(mathematicalPick.toVariantMap());
if (!pick->isEnabled() || pick->getFilter().doesPickNothing() || pick->getMaxDistance() < 0.0f || !mathematicalPick) {
pick->setPickResult(res);
continue;
}
if (pick->getFilter().doesPickEntities()) {
PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) {
PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick);
if (entityRes) {
cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick);
}
}
}
if (pick->getFilter().doesPickOverlays()) {
PickCacheKey overlayKey = { pick->getFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) {
PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick);
if (overlayRes) {
cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick);
}
}
}
if (pick->getFilter().doesPickAvatars()) {
PickCacheKey avatarKey = { pick->getFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) {
PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick);
if (avatarRes) {
cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick);
}
}
}
// Can't intersect with HUD in desktop mode
if (pick->getFilter().doesPickHUD() && shouldPickHUD) {
PickCacheKey hudKey = { pick->getFilter().getHUDFlags(), QVector<QUuid>(), QVector<QUuid>() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) {
PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick);
if (hudRes) {
cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick);
}
}
}
if (pick->getMaxDistance() == 0.0f || (pick->getMaxDistance() > 0.0f && res->checkOrFilterAgainstMaxDistance(pick->getMaxDistance()))) {
pick->setPickResult(res);
} else {
pick->setPickResult(pick->getDefaultResult(mathematicalPick.toVariantMap()));
if (pick->getFilter().doesPickEntities()) {
PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) {
PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick);
if (entityRes) {
cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick);
}
}
}
if (pick->getFilter().doesPickOverlays()) {
PickCacheKey overlayKey = { pick->getFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) {
PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick);
if (overlayRes) {
cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick);
}
}
}
if (pick->getFilter().doesPickAvatars()) {
PickCacheKey avatarKey = { pick->getFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) {
PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick);
if (avatarRes) {
cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick);
}
}
}
// Can't intersect with HUD in desktop mode
if (pick->getFilter().doesPickHUD() && shouldPickHUD) {
PickCacheKey hudKey = { pick->getFilter().getHUDFlags(), QVector<QUuid>(), QVector<QUuid>() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) {
PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick);
if (hudRes) {
cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick);
}
}
}
if (pick->getMaxDistance() == 0.0f || (pick->getMaxDistance() > 0.0f && res->checkOrFilterAgainstMaxDistance(pick->getMaxDistance()))) {
pick->setPickResult(res);
} else {
pick->setPickResult(pick->getDefaultResult(mathematicalPick.toVariantMap()));
}
}
++itr;
if (itr == picks.end()) {
itr = picks.begin();
}
nextToUpdate = itr->first;
++numUpdates;
if (usecTimestampNow() > expiry) {
break;
}
}
}
#endif // hifi_PickCacheOptimizer_h
#endif // hifi_PickCacheOptimizer_h

View file

@ -89,14 +89,17 @@ void PickManager::setIncludeItems(unsigned int uid, const QVector<QUuid>& includ
}
void PickManager::update() {
uint64_t expiry = usecTimestampNow() + _perFrameTimeBudget;
std::unordered_map<PickQuery::PickType, std::unordered_map<unsigned int, std::shared_ptr<PickQuery>>> cachedPicks;
withReadLock([&] {
cachedPicks = _picks;
});
bool shouldPickHUD = _shouldPickHUDOperator();
_rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], shouldPickHUD);
_stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], false);
// we pass the same expiry to both updates, but the stylus updates are relatively cheap
// and the rayPicks updae will ALWAYS update at least one ray even when there is no budget
_stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false);
_rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD);
}
bool PickManager::isLeftHand(unsigned int uid) {
@ -121,4 +124,4 @@ bool PickManager::isMouse(unsigned int uid) {
return pick->isMouse();
}
return false;
}
}

View file

@ -14,6 +14,8 @@
#include "Pick.h"
#include "PickCacheOptimizer.h"
#include <NumericalConstants.h>
class PickManager : public Dependency, protected ReadWriteLockable {
SINGLETON_DEPENDENCY
@ -48,17 +50,24 @@ public:
static const unsigned int INVALID_PICK_ID { 0 };
unsigned int getPerFrameTimeBudget() const { return _perFrameTimeBudget; }
void setPerFrameTimeBudget(unsigned int numUsecs) { _perFrameTimeBudget = numUsecs; }
protected:
std::function<bool()> _shouldPickHUDOperator;
std::function<glm::vec2(const glm::vec3&)> _calculatePos2DFromHUDOperator;
std::shared_ptr<PickQuery> findPick(unsigned int uid) const;
std::unordered_map<PickQuery::PickType, std::unordered_map<unsigned int, std::shared_ptr<PickQuery>>> _picks;
unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0 };
std::unordered_map<unsigned int, PickQuery::PickType> _typeMap;
unsigned int _nextPickID { INVALID_PICK_ID + 1 };
PickCacheOptimizer<PickRay> _rayPickCacheOptimizer;
PickCacheOptimizer<StylusTip> _stylusPickCacheOptimizer;
static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 2 * USECS_PER_MSEC;
unsigned int _perFrameTimeBudget { DEFAULT_PER_FRAME_TIME_BUDGET };
};
#endif // hifi_PickManager_h
#endif // hifi_PickManager_h

View file

@ -67,3 +67,11 @@ bool AudioScriptingInterface::setStereoInput(bool stereo) {
}
return stereoInputChanged;
}
bool AudioScriptingInterface::isStereoInput() {
bool stereoEnabled = false;
if (_localAudioInterface) {
stereoEnabled = _localAudioInterface->isStereoInput();
}
return stereoEnabled;
}

View file

@ -36,6 +36,7 @@ protected:
Q_INVOKABLE ScriptAudioInjector* playSystemSound(SharedSoundPointer sound, const QVector3D& position);
Q_INVOKABLE bool setStereoInput(bool stereo);
Q_INVOKABLE bool isStereoInput();
signals:
void mutedByMixer(); /// the client has been muted by the mixer

View file

@ -68,6 +68,10 @@ void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool
if (path.contains("vr.google.com/downloads")) {
isZip = true;
}
if (!hasModel(fileList)) {
isZip = false;
}
emit unzipResult(path, fileList, autoAdd, isZip, isBlocks);
}
@ -107,6 +111,15 @@ bool FileScriptingInterface::isTempDir(QString tempDir) {
return (testContainer == tempContainer);
}
bool FileScriptingInterface::hasModel(QStringList fileList) {
for (int i = 0; i < fileList.size(); i++) {
if (fileList.at(i).toLower().contains(".fbx") || fileList.at(i).toLower().contains(".obj")) {
return true;
}
}
return false;
}
QString FileScriptingInterface::getTempDir() {
QTemporaryDir dir;
dir.setAutoRemove(false);

View file

@ -32,6 +32,7 @@ signals:
private:
bool isTempDir(QString tempDir);
bool hasModel(QStringList fileList);
QStringList unzipFile(QString path, QString tempDir);
void recursiveFileScan(QFileInfo file, QString* dirName);
void downloadZip(QString path, const QString link);

View file

@ -287,6 +287,15 @@ bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct
return false;
}
bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const {
glm::vec3 localCenter = calcCenter() - origin;
float distance = glm::dot(localCenter, direction);
const float ONE_OVER_TWO_SQUARED = 0.25f;
float radiusSquared = ONE_OVER_TWO_SQUARED * glm::length2(_scale);
return (glm::length2(localCenter) < radiusSquared
|| (glm::abs(distance) > 0.0f && glm::distance2(distance * direction, localCenter) < radiusSquared));
}
bool AABox::touchesSphere(const glm::vec3& center, float radius) const {
// Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf
glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - _scale, Vectors::ZERO);

View file

@ -71,6 +71,7 @@ public:
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal) const;
bool rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const;
bool touchesSphere(const glm::vec3& center, float radius) const; // fast but may generate false positives
bool touchesAAEllipsoid(const glm::vec3& center, const glm::vec3& radials) const;
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const;

View file

@ -22,7 +22,6 @@
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
#include <QtCore/QMutexLocker>
#include <QtCore/QRegExp>
#include <QtCore/QThread>
#include <QtCore/QTimer>
@ -92,12 +91,13 @@ void LogHandler::setShouldDisplayMilliseconds(bool shouldDisplayMilliseconds) {
void LogHandler::flushRepeatedMessages() {
QMutexLocker lock(&_mutex);
QHash<QString, int>::iterator message = _repeatMessageCountHash.begin();
while (message != _repeatMessageCountHash.end()) {
for(auto& message: _repeatedMessages) {
if (message.value() > 0) {
if (message.messageCount > 1) {
QString repeatMessage = QString("%1 repeated log entries matching \"%2\" - Last entry: \"%3\"")
.arg(message.value()).arg(message.key()).arg(_lastRepeatedMessage.value(message.key()));
.arg(message.messageCount - 1)
.arg(message.regexp.pattern())
.arg(message.lastMessage);
QMessageLogContext emptyContext;
lock.unlock();
@ -105,8 +105,7 @@ void LogHandler::flushRepeatedMessages() {
lock.relock();
}
_lastRepeatedMessage.remove(message.key());
message = _repeatMessageCountHash.erase(message);
message.messageCount = 0;
}
}
@ -118,36 +117,24 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont
if (type == LogDebug) {
// for debug messages, check if this matches any of our regexes for repeated log messages
foreach(const QString& regexString, getInstance()._repeatedMessageRegexes) {
QRegExp repeatRegex(regexString);
if (repeatRegex.indexIn(message) != -1) {
if (!_repeatMessageCountHash.contains(regexString)) {
// we have a match but didn't have this yet - output the first one
_repeatMessageCountHash[regexString] = 0;
// break the foreach so we output the first match
for (auto& repeatRegex : _repeatedMessages) {
if (repeatRegex.regexp.indexIn(message) != -1) {
// If we've printed the first one then return out.
if (repeatRegex.messageCount++ == 0) {
break;
} else {
// we have a match - add 1 to the count of repeats for this message and set this as the last repeated message
_repeatMessageCountHash[regexString] += 1;
_lastRepeatedMessage[regexString] = message;
// return out, we're not printing this one
return QString();
}
repeatRegex.lastMessage = message;
return QString();
}
}
}
if (type == LogDebug) {
// see if this message is one we should only print once
foreach(const QString& regexString, getInstance()._onlyOnceMessageRegexes) {
QRegExp onlyOnceRegex(regexString);
if (onlyOnceRegex.indexIn(message) != -1) {
if (!_onlyOnceMessageCountHash.contains(message)) {
for (auto& onceOnly : _onetimeMessages) {
if (onceOnly.regexp.indexIn(message) != -1) {
if (onceOnly.messageCount++ == 0) {
// we have a match and haven't yet printed this message.
_onlyOnceMessageCountHash[message] = 1;
// break the foreach so we output the first match
break;
} else {
// We've already printed this message, don't print it again.
@ -217,10 +204,16 @@ const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) {
QMetaObject::invokeMethod(this, "setupRepeatedMessageFlusher");
QMutexLocker lock(&_mutex);
return *_repeatedMessageRegexes.insert(regexString);
RepeatedMessage repeatRecord;
repeatRecord.regexp = QRegExp(regexString);
_repeatedMessages.push_back(repeatRecord);
return regexString;
}
const QString& LogHandler::addOnlyOnceMessageRegex(const QString& regexString) {
QMutexLocker lock(&_mutex);
return *_onlyOnceMessageRegexes.insert(regexString);
OnceOnlyMessage onetimeMessage;
onetimeMessage.regexp = QRegExp(regexString);
_onetimeMessages.push_back(onetimeMessage);
return regexString;
}

View file

@ -13,11 +13,12 @@
#ifndef hifi_LogHandler_h
#define hifi_LogHandler_h
#include <QHash>
#include <QObject>
#include <QSet>
#include <QString>
#include <QRegExp>
#include <QMutex>
#include <vector>
#include <memory>
const int VERBOSE_LOG_INTERVAL_SECONDS = 5;
@ -66,12 +67,19 @@ private:
bool _shouldOutputProcessID { false };
bool _shouldOutputThreadID { false };
bool _shouldDisplayMilliseconds { false };
QSet<QString> _repeatedMessageRegexes;
QHash<QString, int> _repeatMessageCountHash;
QHash<QString, QString> _lastRepeatedMessage;
QSet<QString> _onlyOnceMessageRegexes;
QHash<QString, int> _onlyOnceMessageCountHash;
struct RepeatedMessage {
QRegExp regexp;
int messageCount { 0 };
QString lastMessage;
};
std::vector<RepeatedMessage> _repeatedMessages;
struct OnceOnlyMessage {
QRegExp regexp;
int messageCount { 0 };
};
std::vector<OnceOnlyMessage> _onetimeMessages;
static QMutex _mutex;
};

View file

@ -313,7 +313,7 @@
<label>ID:</label>
<input type="text" id="property-id" readonly>
</div>
<div class="property checkbox">
<div class="can-cast-shadow-section property checkbox">
<input type="checkbox" id="property-can-cast-shadow">
<label for="property-can-cast-shadow">Can cast shadow</label>
</div>

View file

@ -272,17 +272,19 @@ SelectionDisplay = (function() {
var STRETCH_SPHERE_OFFSET = 0.06;
var STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE = 0.01;
var STRETCH_MINIMUM_DIMENSION = 0.001;
var STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE = 2;
var STRETCH_ALL_MINIMUM_DIMENSION = 0.01;
var STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE = 6;
var STRETCH_PANEL_WIDTH = 0.01;
var SCALE_CUBE_OFFSET = 0.5;
var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.015;
var SCALE_MINIMUM_DIMENSION = 0.02;
var CLONER_OFFSET = { x:0.9, y:-0.9, z:0.9 };
var CTRL_KEY_CODE = 16777249;
var AVATAR_COLLISIONS_OPTION = "Enable Avatar Collisions";
var TRANSLATE_DIRECTION = {
X : 0,
Y : 1,
@ -336,6 +338,8 @@ SelectionDisplay = (function() {
var ctrlPressed = false;
var handleStretchCollisionOverride = false;
var handlePropertiesTranslateArrowCones = {
shape: "Cone",
solid: true,
@ -458,12 +462,12 @@ SelectionDisplay = (function() {
borderSize: 1.4
};
var handleScaleLBNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, -y, -z)
var handleScaleRBNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, -y, z)
var handleScaleLBFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, -y, -z)
var handleScaleRBNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, -y, -z)
var handleScaleLBFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, -y, z)
var handleScaleRBFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, -y, z)
var handleScaleLTNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, y, -z)
var handleScaleRTNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, y, z)
var handleScaleLTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, -z)
var handleScaleRTNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, -z)
var handleScaleLTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, y, z)
var handleScaleRTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, z)
var handlePropertiesScaleEdge = {
@ -597,6 +601,11 @@ SelectionDisplay = (function() {
var activeTool = null;
var handleTools = {};
that.shutdown = function() {
that.restoreAvatarCollisionsFromStretch();
}
Script.scriptEnding.connect(that.shutdown);
// We get mouseMoveEvents from the handControllers, via handControllerPointer.
// But we dont' get mousePressEvents.
that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
@ -1021,7 +1030,6 @@ SelectionDisplay = (function() {
return;
}
if (SelectionManager.hasSelection()) {
var position = SelectionManager.worldPosition;
var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation;
@ -1147,14 +1155,14 @@ SelectionDisplay = (function() {
rotation: scaleCubeRotation,
dimensions: scaleCubeDimensions
});
var scaleRBNCubePosition = { x:-scaleCubeOffsetX, y:-scaleCubeOffsetY, z:scaleCubeOffsetZ };
var scaleRBNCubePosition = { x:scaleCubeOffsetX, y:-scaleCubeOffsetY, z:-scaleCubeOffsetZ };
scaleRBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBNCubePosition));
Overlays.editOverlay(handleScaleRBNCube, {
position: scaleRBNCubePosition,
rotation: scaleCubeRotation,
dimensions: scaleCubeDimensions
});
var scaleLBFCubePosition = { x:scaleCubeOffsetX, y:-scaleCubeOffsetY, z:-scaleCubeOffsetZ };
var scaleLBFCubePosition = { x:-scaleCubeOffsetX, y:-scaleCubeOffsetY, z:scaleCubeOffsetZ };
scaleLBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBFCubePosition));
Overlays.editOverlay(handleScaleLBFCube, {
position: scaleLBFCubePosition,
@ -1175,14 +1183,14 @@ SelectionDisplay = (function() {
rotation: scaleCubeRotation,
dimensions: scaleCubeDimensions
});
var scaleRTNCubePosition = { x:-scaleCubeOffsetX, y:scaleCubeOffsetY, z:scaleCubeOffsetZ };
var scaleRTNCubePosition = { x:scaleCubeOffsetX, y:scaleCubeOffsetY, z:-scaleCubeOffsetZ };
scaleRTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTNCubePosition));
Overlays.editOverlay(handleScaleRTNCube, {
position: scaleRTNCubePosition,
rotation: scaleCubeRotation,
dimensions: scaleCubeDimensions
});
var scaleLTFCubePosition = { x:scaleCubeOffsetX, y:scaleCubeOffsetY, z:-scaleCubeOffsetZ };
var scaleLTFCubePosition = { x:-scaleCubeOffsetX, y:scaleCubeOffsetY, z:scaleCubeOffsetZ };
scaleLTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTFCubePosition));
Overlays.editOverlay(handleScaleLTFCube, {
position: scaleLTFCubePosition,
@ -1236,9 +1244,11 @@ SelectionDisplay = (function() {
});
// UPDATE STRETCH HIGHLIGHT PANELS
var scaleLTFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleLTFCubePosition);
var scaleRBFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRBFCubePosition);
var stretchPanelXDimensions = Vec3.subtract(scaleLTFCubePositionRotated, scaleRBFCubePositionRotated);
var scaleRTFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTFCubePosition);
var scaleLTNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleLTNCubePosition);
var scaleRTNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTNCubePosition);
var stretchPanelXDimensions = Vec3.subtract(scaleRTNCubePositionRotated, scaleRBFCubePositionRotated);
var tempY = Math.abs(stretchPanelXDimensions.y);
stretchPanelXDimensions.x = STRETCH_PANEL_WIDTH;
stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z);
@ -1249,8 +1259,6 @@ SelectionDisplay = (function() {
rotation: rotationZ,
dimensions: stretchPanelXDimensions
});
var scaleLTNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleLTNCubePosition);
var scaleRTFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTFCubePosition);
var stretchPanelYDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRTFCubePositionRotated);
var tempX = Math.abs(stretchPanelYDimensions.x);
stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z);
@ -1262,9 +1270,7 @@ SelectionDisplay = (function() {
rotation: rotationY,
dimensions: stretchPanelYDimensions
});
var scaleRTFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTFCubePosition);
var scaleRBNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRBNCubePosition);
var stretchPanelZDimensions = Vec3.subtract(scaleRTFCubePositionRotated, scaleRBNCubePositionRotated);
var stretchPanelZDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRBFCubePositionRotated);
var tempX = Math.abs(stretchPanelZDimensions.x);
stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y);
stretchPanelZDimensions.y = tempX;
@ -1743,6 +1749,13 @@ SelectionDisplay = (function() {
};
};
that.restoreAvatarCollisionsFromStretch = function() {
if (handleStretchCollisionOverride) {
Menu.setIsOptionChecked(AVATAR_COLLISIONS_OPTION, true);
handleStretchCollisionOverride = false;
}
}
// TOOL DEFINITION: HANDLE STRETCH TOOL
function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset, stretchPanel, scaleHandle) {
var directionFor3DStretch = directionVec;
@ -1945,6 +1958,10 @@ SelectionDisplay = (function() {
if (scaleHandle != null) {
Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE_SELECTED });
}
if (Menu.isOptionChecked(AVATAR_COLLISIONS_OPTION)) {
Menu.setIsOptionChecked(AVATAR_COLLISIONS_OPTION, false);
handleStretchCollisionOverride = true;
}
};
var onEnd = function(event, reason) {
@ -1954,11 +1971,12 @@ SelectionDisplay = (function() {
if (scaleHandle != null) {
Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE });
}
that.restoreAvatarCollisionsFromStretch();
pushCommandForSelections();
};
var onMove = function(event) {
var proportional = (spaceMode === SPACE_WORLD) || event.isShifted || directionEnum === STRETCH_DIRECTION.ALL;
var proportional = (spaceMode === SPACE_WORLD) || directionEnum === STRETCH_DIRECTION.ALL;
var position, dimensions, rotation;
if (spaceMode === SPACE_LOCAL) {
@ -1999,10 +2017,10 @@ SelectionDisplay = (function() {
vector = grid.snapToSpacing(vector);
var changeInDimensions = Vec3.multiply(NEGATE_VECTOR, vec3Mult(localSigns, vector));
if (directionEnum === STRETCH_DIRECTION.ALL) {
var toCameraDistance = getDistanceToCamera(position);
var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE;
changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple);
if (directionEnum === STRETCH_DIRECTION.ALL) {
var toCameraDistance = getDistanceToCamera(position);
var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE;
changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple);
}
var newDimensions;
@ -2027,9 +2045,11 @@ SelectionDisplay = (function() {
newDimensions = Vec3.sum(initialDimensions, changeInDimensions);
}
newDimensions.x = Math.max(newDimensions.x, STRETCH_MINIMUM_DIMENSION);
newDimensions.y = Math.max(newDimensions.y, STRETCH_MINIMUM_DIMENSION);
newDimensions.z = Math.max(newDimensions.z, STRETCH_MINIMUM_DIMENSION);
var minimumDimension = directionEnum === STRETCH_DIRECTION.ALL ? STRETCH_ALL_MINIMUM_DIMENSION :
STRETCH_MINIMUM_DIMENSION;
newDimensions.x = Math.max(newDimensions.x, minimumDimension);
newDimensions.y = Math.max(newDimensions.y, minimumDimension);
newDimensions.z = Math.max(newDimensions.z, minimumDimension);
var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions));
if (directionEnum === STRETCH_DIRECTION.ALL) {
@ -2089,10 +2109,10 @@ SelectionDisplay = (function() {
directionVector = { x:1, y:1, z:1 };
selectedHandle = handleScaleLBNCube;
} else if (directionEnum === SCALE_DIRECTION.RBN) {
directionVector = { x:1, y:1, z:-1 };
directionVector = { x:-1, y:1, z:1 };
selectedHandle = handleScaleRBNCube;
} else if (directionEnum === SCALE_DIRECTION.LBF) {
directionVector = { x:-1, y:1, z:1 };
directionVector = { x:1, y:1, z:-1 };
selectedHandle = handleScaleLBFCube;
} else if (directionEnum === SCALE_DIRECTION.RBF) {
directionVector = { x:-1, y:1, z:-1 };
@ -2101,10 +2121,10 @@ SelectionDisplay = (function() {
directionVector = { x:1, y:-1, z:1 };
selectedHandle = handleScaleLTNCube;
} else if (directionEnum === SCALE_DIRECTION.RTN) {
directionVector = { x:1, y:-1, z:-1 };
directionVector = { x:-1, y:-1, z:1 };
selectedHandle = handleScaleRTNCube;
} else if (directionEnum === SCALE_DIRECTION.LTF) {
directionVector = { x:-1, y:-1, z:1 };
directionVector = { x:1, y:-1, z:-1 };
selectedHandle = handleScaleLTFCube;
} else if (directionEnum === SCALE_DIRECTION.RTF) {
directionVector = { x:-1, y:-1, z:-1 };

View file

@ -439,8 +439,9 @@ HifiEntityUI.prototype = {
$colPickContainer.colpick({
colorScheme: 'dark',
layout: 'hex',
colorScheme: (group.layoutColorScheme === undefined ? 'dark' : group.layoutColorScheme),
layout: (group.layoutType === undefined ? 'hex' : group.layoutType),
submit: (group.useSubmitButton === undefined ? true : group.useSubmitButton),
color: {
r: domArray[0].value,
g: domArray[1].value,

View file

@ -27,6 +27,7 @@
<link rel="stylesheet" type="text/css" href="../html/css/hifi-style.css">
<link rel="stylesheet" type="text/css" href="../html/css/edit-style.css">
<link rel="stylesheet" type="text/css" href="../html/css/colpick.css">
<link rel="stylesheet" type="text/css" href="particle-style.css">
</head>
<body>

View file

@ -236,7 +236,10 @@
red: 255,
green: 255,
blue: 255
}
},
layoutType: "hex",
layoutColorScheme: "dark",
useSubmitButton: false
},
{
type: "Row"
@ -249,7 +252,10 @@
red: 0,
green: 0,
blue: 0
}
},
layoutType: "hex",
layoutColorScheme: "dark",
useSubmitButton: false
},
{
type: "Row"
@ -262,7 +268,10 @@
red: 255,
green: 255,
blue: 255
}
},
layoutType: "hex",
layoutColorScheme: "dark",
useSubmitButton: false
},
{
type: "Row"
@ -275,7 +284,10 @@
red: 255,
green: 255,
blue: 255
}
},
layoutType: "hex",
layoutColorScheme: "dark",
useSubmitButton: false
},
{
type: "Row"

View file

@ -5,7 +5,7 @@ project(${TARGET_NAME})
SET (CMAKE_AUTOUIC ON)
SET (CMAKE_AUTOMOC ON)
setup_hifi_project (Core Widgets)
setup_hifi_project (Core Widgets Network)
link_hifi_libraries ()
# FIX: Qt was built with -reduce-relocations

View file

@ -0,0 +1,32 @@
//
// Downloader.cpp
//
// Created by Nissim Hadar on 1 Mar 2018.
// Copyright 2013 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 "Downloader.h"
Downloader::Downloader(QUrl imageUrl, QObject *parent) : QObject(parent) {
connect(
&_networkAccessManager, SIGNAL (finished(QNetworkReply*)),
this, SLOT (fileDownloaded(QNetworkReply*))
);
QNetworkRequest request(imageUrl);
_networkAccessManager.get(request);
}
void Downloader::fileDownloaded(QNetworkReply* reply) {
_downloadedData = reply->readAll();
//emit a signal
reply->deleteLater();
emit downloaded();
}
QByteArray Downloader::downloadedData() const {
return _downloadedData;
}

View file

@ -0,0 +1,48 @@
//
// Downloader.h
//
// Created by Nissim Hadar on 1 Mar 2018.
// Copyright 2013 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_downloader_h
#define hifi_downloader_h
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include <QDateTime>
#include <QFile>
#include <QFileInfo>
#include <QDebug>
#include <QObject>
#include <QByteArray>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
class Downloader : public QObject {
Q_OBJECT
public:
explicit Downloader(QUrl imageUrl, QObject *parent = 0);
QByteArray downloadedData() const;
signals:
void downloaded();
private slots:
void fileDownloaded(QNetworkReply* pReply);
private:
QNetworkAccessManager _networkAccessManager;
QByteArray _downloadedData;
};
#endif // hifi_downloader_h

View file

@ -17,13 +17,13 @@
double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) const {
// Make sure the image is 8 bits per colour
QImage::Format format = expectedImage.format();
if (format != QImage::Format::Format_RGB32) {
if (format != QImage::Format::Format_ARGB32) {
throw -1;
}
const int L = 255; // (2^number of bits per pixel) - 1
const double K1{ 0.01 };
const double K2{ 0.03 };
const double K1 { 0.01 };
const double K2 { 0.03 };
const double c1 = pow((K1 * L), 2);
const double c2 = pow((K2 * L), 2);

View file

@ -12,33 +12,32 @@
#include <assert.h>
#include <QtCore/QTextStream>
#include <QDirIterator>
#include <QImageReader>
#include <QImageWriter>
#include <quazip5/quazip.h>
#include <quazip5/JlCompress.h>
Test::Test() {
snapshotFilenameFormat = QRegularExpression("hifi-snap-by-.*-on-\\d\\d\\d\\d-\\d\\d-\\d\\d_\\d\\d-\\d\\d-\\d\\d.jpg");
#include "ui/AutoTester.h"
extern AutoTester* autoTester;
expectedImageFilenameFormat = QRegularExpression("ExpectedImage_\\d+.jpg");
#include <math.h>
Test::Test() {
QString regex(EXPECTED_IMAGE_PREFIX + QString("\\\\d").repeated(NUM_DIGITS) + ".png");
expectedImageFilenameFormat = QRegularExpression(regex);
mismatchWindow.setModal(true);
}
bool Test::createTestResultsFolderPathIfNeeded(QString directory) {
// The test results folder is located in the root of the tests (i.e. for recursive test evaluation)
if (testResultsFolderPath == "") {
testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER;
QDir testResultsFolder(testResultsFolderPath);
bool Test::createTestResultsFolderPath(QString directory) {
QDateTime now = QDateTime::currentDateTime();
testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT);
QDir testResultsFolder(testResultsFolderPath);
if (testResultsFolder.exists()) {
testResultsFolder.removeRecursively();
}
// Create a new test results folder
return QDir().mkdir(testResultsFolderPath);
} else {
return true;
}
// Create a new test results folder
return QDir().mkdir(testResultsFolderPath);
}
void Test::zipAndDeleteTestResultsFolder() {
@ -60,9 +59,9 @@ void Test::zipAndDeleteTestResultsFolder() {
index = 1;
}
bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar) {
bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) {
progressBar->setMinimum(0);
progressBar->setMaximum(expectedImages.length() - 1);
progressBar->setMaximum(expectedImagesFullFilenames.length() - 1);
progressBar->setValue(0);
progressBar->setVisible(true);
@ -71,12 +70,13 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage
const double THRESHOLD { 0.999 };
bool success{ true };
bool keepOn{ true };
for (int i = 0; keepOn && i < expectedImages.length(); ++i) {
for (int i = 0; keepOn && i < expectedImagesFullFilenames.length(); ++i) {
// First check that images are the same size
QImage resultImage(resultImages[i]);
QImage expectedImage(expectedImages[i]);
QImage resultImage(resultImagesFullFilenames[i]);
QImage expectedImage(expectedImagesFullFilenames[i]);
if (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height()) {
messageBox.critical(0, "Internal error", "Images are not the same size");
messageBox.critical(0, "Internal error #1", "Images are not the same size");
exit(-1);
}
@ -84,21 +84,21 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage
try {
similarityIndex = imageComparer.compareImages(resultImage, expectedImage);
} catch (...) {
messageBox.critical(0, "Internal error", "Image not in expected format");
messageBox.critical(0, "Internal error #2", "Image not in expected format");
exit(-1);
}
if (similarityIndex < THRESHOLD) {
TestFailure testFailure = TestFailure{
(float)similarityIndex,
expectedImages[i].left(expectedImages[i].lastIndexOf("/") + 1), // path to the test (including trailing /)
QFileInfo(expectedImages[i].toStdString().c_str()).fileName(), // filename of expected image
QFileInfo(resultImages[i].toStdString().c_str()).fileName() // filename of result image
expectedImagesFullFilenames[i].left(expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /)
QFileInfo(expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image
QFileInfo(resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image
};
mismatchWindow.setTestFailure(testFailure);
if (!interactiveMode) {
if (!isInteractiveMode) {
appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage());
success = false;
} else {
@ -131,20 +131,20 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage
void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) {
if (!QDir().exists(testResultsFolderPath)) {
messageBox.critical(0, "Internal error", "Folder " + testResultsFolderPath + " not found");
messageBox.critical(0, "Internal error #3", "Folder " + testResultsFolderPath + " not found");
exit(-1);
}
QString failureFolderPath { testResultsFolderPath + "/" + "Failure_" + QString::number(index) };
if (!QDir().mkdir(failureFolderPath)) {
messageBox.critical(0, "Internal error", "Failed to create folder " + failureFolderPath);
messageBox.critical(0, "Internal error #4", "Failed to create folder " + failureFolderPath);
exit(-1);
}
++index;
QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME);
if (!descriptionFile.open(QIODevice::ReadWrite)) {
messageBox.critical(0, "Internal error", "Failed to create file " + TEST_RESULTS_FILENAME);
messageBox.critical(0, "Internal error #5", "Failed to create file " + TEST_RESULTS_FILENAME);
exit(-1);
}
@ -164,60 +164,91 @@ void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure te
sourceFile = testFailure._pathname + testFailure._expectedImageFilename;
destinationFile = failureFolderPath + "/" + "Expected Image.jpg";
if (!QFile::copy(sourceFile, destinationFile)) {
messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile);
messageBox.critical(0, "Internal error #6", "Failed to copy " + sourceFile + " to " + destinationFile);
exit(-1);
}
sourceFile = testFailure._pathname + testFailure._actualImageFilename;
destinationFile = failureFolderPath + "/" + "Actual Image.jpg";
if (!QFile::copy(sourceFile, destinationFile)) {
messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile);
messageBox.critical(0, "Internal error #7", "Failed to copy " + sourceFile + " to " + destinationFile);
exit(-1);
}
comparisonImage.save(failureFolderPath + "/" + "Difference Image.jpg");
}
void Test::evaluateTests(bool interactiveMode, QProgressBar* progressBar) {
void Test::startTestsEvaluation() {
// Get list of JPEG images in folder, sorted by name
QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly);
if (pathToImageDirectory == "") {
pathToTestResultsDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly);
if (pathToTestResultsDirectory == "") {
return;
}
// Leave if test results folder could not be created
if (!createTestResultsFolderPathIfNeeded(pathToImageDirectory)) {
// Quit if test results folder could not be created
if (!createTestResultsFolderPath(pathToTestResultsDirectory)) {
return;
}
QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory);
// Before any processing - all images are converted to PNGs, as this is the format stored on GitHub
QStringList sortedSnapshotFilenames = createListOfAll_imagesInDirectory("jpg", pathToTestResultsDirectory);
foreach(QString filename, sortedSnapshotFilenames) {
QStringList stringParts = filename.split(".");
copyJPGtoPNG(
pathToTestResultsDirectory + "/" + stringParts[0] + ".jpg",
pathToTestResultsDirectory + "/" + stringParts[0] + ".png"
);
// Separate images into two lists. The first is the expected images, the second is the test results
QFile::remove(pathToTestResultsDirectory + "/" + stringParts[0] + ".jpg");
}
// Create two lists. The first is the test results, the second is the expected images
// The expected images are represented as a URL to enable download from GitHub
// Images that are in the wrong format are ignored.
QStringList expectedImages;
QStringList resultImages;
foreach(QString currentFilename, sortedImageFilenames) {
QString fullCurrentFilename = pathToImageDirectory + "/" + currentFilename;
if (isInExpectedImageFilenameFormat(currentFilename)) {
expectedImages << fullCurrentFilename;
} else if (isInSnapshotFilenameFormat(currentFilename)) {
resultImages << fullCurrentFilename;
QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", pathToTestResultsDirectory);
QStringList expectedImagesURLs;
const QString URLPrefix("https://raw.githubusercontent.com");
const QString githubUser("NissimHadar");
const QString testsRepo("hifi_tests");
const QString branch("addRecursionToAutotester");
resultImagesFullFilenames.clear();
expectedImagesFilenames.clear();
expectedImagesFullFilenames.clear();
foreach(QString currentFilename, sortedTestResultsFilenames) {
QString fullCurrentFilename = pathToTestResultsDirectory + "/" + currentFilename;
if (isInSnapshotFilenameFormat("png", currentFilename)) {
resultImagesFullFilenames << fullCurrentFilename;
QString expectedImagePartialSourceDirectory = getExpectedImagePartialSourceDirectory(currentFilename);
// Images are stored on GitHub as ExpectedImage_ddddd.png
// Extract the digits at the end of the filename (exluding the file extension)
QString expectedImageFilenameTail = currentFilename.left(currentFilename.length() - 4).right(NUM_DIGITS);
QString expectedImageStoredFilename = EXPECTED_IMAGE_PREFIX + expectedImageFilenameTail + ".png";
QString imageURLString(URLPrefix + "/" + githubUser + "/" + testsRepo + "/" + branch + "/" +
expectedImagePartialSourceDirectory + "/" + expectedImageStoredFilename);
expectedImagesURLs << imageURLString;
// The image retrieved from Github needs a unique name
QString expectedImageFilename = currentFilename.replace("/", "_").replace(".", "_EI.");
expectedImagesFilenames << expectedImageFilename;
expectedImagesFullFilenames << pathToTestResultsDirectory + "/" + expectedImageFilename;
}
}
// The number of images in each list should be identical
if (expectedImages.length() != resultImages.length()) {
messageBox.critical(0,
"Test failed",
"Found " + QString::number(resultImages.length()) + " images in directory" +
"\nExpected to find " + QString::number(expectedImages.length()) + " images"
);
exit(-1);
}
bool success = compareImageLists(expectedImages, resultImages, pathToImageDirectory, interactiveMode, progressBar);
autoTester->downloadImages(expectedImagesURLs, pathToTestResultsDirectory, expectedImagesFilenames);
}
void Test::finishTestsEvaluation(bool interactiveMode, QProgressBar* progressBar) {
bool success = compareImageLists(interactiveMode, progressBar);
if (success) {
messageBox.information(0, "Success", "All images are as expected");
} else {
@ -242,72 +273,25 @@ bool Test::isAValidDirectory(QString pathname) {
return true;
}
// Two criteria are used to decide if a folder contains valid test results.
// 1) a 'test'js' file exists in the folder
// 2) the folder has the same number of actual and expected images
void Test::evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar) {
// Select folder to start recursing from
QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly);
if (topLevelDirectory == "") {
return;
}
// Leave if test results folder could not be created
if (!createTestResultsFolderPathIfNeeded(topLevelDirectory)) {
return;
}
bool success{ true };
QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
if (!isAValidDirectory(directory)) {
continue;
}
const QString testPathname{ directory + "/" + TEST_FILENAME };
QFileInfo fileInfo(testPathname);
if (!fileInfo.exists()) {
// Folder does not contain 'test.js'
continue;
}
QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(directory);
// Separate images into two lists. The first is the expected images, the second is the test results
// Images that are in the wrong format are ignored.
QStringList expectedImages;
QStringList resultImages;
foreach(QString currentFilename, sortedImageFilenames) {
QString fullCurrentFilename = directory + "/" + currentFilename;
if (isInExpectedImageFilenameFormat(currentFilename)) {
expectedImages << fullCurrentFilename;
} else if (isInSnapshotFilenameFormat(currentFilename)) {
resultImages << fullCurrentFilename;
}
}
if (expectedImages.length() != resultImages.length()) {
// Number of images doesn't match
continue;
}
// Set success to false if any test has failed
success &= compareImageLists(expectedImages, resultImages, directory, interactiveMode, progressBar);
}
if (success) {
messageBox.information(0, "Success", "All images are as expected");
} else {
messageBox.information(0, "Failure", "One or more images are not as expected");
}
zipAndDeleteTestResultsFolder();
}
void Test::importTest(QTextStream& textStream, const QString& testPathname) {
textStream << "Script.include(\"" << "file:///" << testPathname + "?raw=true\");" << endl;
// `testPathname` includes the full path to the test. We need the portion below (and including) `tests`
QStringList filenameParts = testPathname.split('/');
int i{ 0 };
while (i < filenameParts.length() && filenameParts[i] != "tests") {
++i;
}
if (i == filenameParts.length()) {
messageBox.critical(0, "Internal error #10", "Bad testPathname");
exit(-1);
}
QString filename;
for (int j = i; j < filenameParts.length(); ++j) {
filename += "/" + filenameParts[j];
}
textStream << "Script.include(\"" << "https://raw.githubusercontent.com/" << user << "/hifi_tests/" << branch << filename + "\");" << endl;
}
// Creates a single script in a user-selected folder.
@ -319,11 +303,58 @@ void Test::createRecursiveScript() {
return;
}
QFile allTestsFilename(topLevelDirectory + "/" + "allTests.js");
createRecursiveScript(topLevelDirectory, true);
}
// This method creates a `testRecursive.js` script in every sub-folder.
void Test::createRecursiveScriptsRecursively() {
// Select folder to start recursing from
QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts", ".", QFileDialog::ShowDirsOnly);
if (topLevelDirectory == "") {
return;
}
createRecursiveScript(topLevelDirectory, false);
QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
// Only process directories
QDir dir;
if (!isAValidDirectory(directory)) {
continue;
}
// Only process directories that have sub-directories
bool hasNoSubDirectories{ true };
QDirIterator it2(directory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it2.hasNext()) {
QString directory2 = it2.next();
// Only process directories
QDir dir;
if (isAValidDirectory(directory2)) {
hasNoSubDirectories = false;
break;
}
}
if (!hasNoSubDirectories) {
createRecursiveScript(directory, false);
}
}
messageBox.information(0, "Success", "Scripts have been created");
}
void Test::createRecursiveScript(QString topLevelDirectory, bool interactiveMode) {
const QString recursiveTestsFilename("testRecursive.js");
QFile allTestsFilename(topLevelDirectory + "/" + recursiveTestsFilename);
if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) {
messageBox.critical(0,
"Internal Error",
"Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\""
"Internal Error #8",
"Failed to create \"" + recursiveTestsFilename + "\" in directory \"" + topLevelDirectory + "\""
);
exit(-1);
@ -332,12 +363,9 @@ void Test::createRecursiveScript() {
QTextStream textStream(&allTestsFilename);
textStream << "// This is an automatically generated file, created by auto-tester" << endl << endl;
textStream << "var autoTester = Script.require(\"https://github.com/highfidelity/hifi_tests/blob/master/tests/utils/autoTester.js?raw=true\");" << endl;
textStream << "var autoTester = Script.require(\"https://raw.githubusercontent.com/" + user + "/hifi_tests/" + branch + "/tests/utils/autoTester.js\");" << endl;
textStream << "autoTester.enableRecursive();" << endl << endl;
// The main will call each test after the previous test is completed
// This is implemented with an interval timer that periodically tests if a
// running test has increment a testNumber variable that it received as an input.
QVector<QString> testPathnames;
// First test if top-level folder has a test.js file
@ -360,7 +388,7 @@ void Test::createRecursiveScript() {
continue;
}
const QString testPathname{ directory + "/" + TEST_FILENAME };
const QString testPathname { directory + "/" + TEST_FILENAME };
QFileInfo fileInfo(testPathname);
if (fileInfo.exists()) {
// Current folder contains a test
@ -370,8 +398,8 @@ void Test::createRecursiveScript() {
}
}
if (testPathnames.length() <= 0) {
messageBox.information(0, "Failure", "No \"test.js\" files found");
if (interactiveMode && testPathnames.length() <= 0) {
messageBox.information(0, "Failure", "No \"" + TEST_FILENAME + "\" files found");
allTestsFilename.close();
return;
}
@ -380,50 +408,45 @@ void Test::createRecursiveScript() {
textStream << "autoTester.runRecursive();" << endl;
allTestsFilename.close();
messageBox.information(0, "Success", "Script has been created");
if (interactiveMode) {
messageBox.information(0, "Success", "Script has been created");
}
}
void Test::createTest() {
// Rename files sequentially, as ExpectedResult_1.jpeg, ExpectedResult_2.jpg and so on
// Rename files sequentially, as ExpectedResult_00000.jpeg, ExpectedResult_00001.jpg and so on
// Any existing expected result images will be deleted
QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly);
if (pathToImageDirectory == "") {
QString imageSourceDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly);
if (imageSourceDirectory == "") {
return;
}
QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory);
QString imageDestinationDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder to save the test images", ".", QFileDialog::ShowDirsOnly);
if (imageDestinationDirectory == "") {
return;
}
int i = 1;
QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("jpg", imageSourceDirectory);
int i = 1;
const int maxImages = pow(10, NUM_DIGITS);
foreach (QString currentFilename, sortedImageFilenames) {
QString fullCurrentFilename = pathToImageDirectory + "/" + currentFilename;
if (isInExpectedImageFilenameFormat(currentFilename)) {
if (!QFile::remove(fullCurrentFilename)) {
QString fullCurrentFilename = imageSourceDirectory + "/" + currentFilename;
if (isInSnapshotFilenameFormat("jpg", currentFilename)) {
if (i >= maxImages) {
messageBox.critical(0, "Error", "More than " + QString::number(maxImages) + " images not supported");
exit(-1);
}
QString newFilename = "ExpectedImage_" + QString::number(i - 1).rightJustified(5, '0') + ".png";
QString fullNewFileName = imageDestinationDirectory + "/" + newFilename;
try {
copyJPGtoPNG(fullCurrentFilename, fullNewFileName);
} catch (...) {
messageBox.critical(0, "Error", "Could not delete existing file: " + currentFilename + "\nTest creation aborted");
exit(-1);
}
} else if (isInSnapshotFilenameFormat(currentFilename)) {
const int MAX_IMAGES = 100000;
if (i >= MAX_IMAGES) {
messageBox.critical(0, "Error", "More than 100,000 images not supported");
exit(-1);
}
QString newFilename = "ExpectedImage_" + QString::number(i-1).rightJustified(5, '0') + ".jpg";
QString fullNewFileName = pathToImageDirectory + "/" + newFilename;
if (!imageDirectory.rename(fullCurrentFilename, newFilename)) {
if (!QFile::exists(fullCurrentFilename)) {
messageBox.critical(0, "Error", "Could not rename file: " + fullCurrentFilename + " to: " + newFilename + "\n"
+ fullCurrentFilename + " not found"
+ "\nTest creation aborted"
);
exit(-1);
} else {
messageBox.critical(0, "Error", "Could not rename file: " + fullCurrentFilename + " to: " + newFilename + "\n"
+ "unknown error" + "\nTest creation aborted"
);
exit(-1);
}
}
++i;
}
}
@ -431,54 +454,87 @@ void Test::createTest() {
messageBox.information(0, "Success", "Test images have been created");
}
void Test::deleteOldSnapshots() {
// Select folder to start recursing from
QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select root folder for snapshot deletion", ".", QFileDialog::ShowDirsOnly);
if (topLevelDirectory == "") {
return;
}
void Test::copyJPGtoPNG(QString sourceJPGFullFilename, QString destinationPNGFullFilename) {
QFile::remove(destinationPNGFullFilename);
// Recurse over folders
QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
QImageReader reader;
reader.setFileName(sourceJPGFullFilename);
// Only process directories
QDir dir(directory);
if (!isAValidDirectory(directory)) {
continue;
}
QImage image = reader.read();
QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(directory);
// Delete any file that is a snapshot (NOT the Expected Images)
QStringList expectedImages;
QStringList resultImages;
foreach(QString currentFilename, sortedImageFilenames) {
QString fullCurrentFilename = directory + "/" + currentFilename;
if (isInSnapshotFilenameFormat(currentFilename)) {
if (!QFile::remove(fullCurrentFilename)) {
messageBox.critical(0, "Error", "Could not delete existing file: " + currentFilename + "\nSnapshot deletion aborted");
exit(-1);
}
}
}
}
QImageWriter writer;
writer.setFileName(destinationPNGFullFilename);
writer.write(image);
}
QStringList Test::createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory) {
QStringList Test::createListOfAll_imagesInDirectory(QString imageFormat, QString pathToImageDirectory) {
imageDirectory = QDir(pathToImageDirectory);
QStringList nameFilters;
nameFilters << "*.jpg";
nameFilters << "*." + imageFormat;
return imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name);
}
// Use regular expressions to check if files are in specific format
bool Test::isInSnapshotFilenameFormat(QString filename) {
return (snapshotFilenameFormat.match(filename).hasMatch());
// Snapshots are files in the following format:
// Filename contains no periods (excluding period before exception
// Filename (i.e. without extension) contains _tests_ (this is based on all test scripts being within the tests folder
// Last 5 characters in filename are digits
// Extension is jpg
bool Test::isInSnapshotFilenameFormat(QString imageFormat, QString filename) {
QStringList filenameParts = filename.split(".");
bool filnameHasNoPeriods = (filenameParts.size() == 2);
bool contains_tests = filenameParts[0].contains("_tests_");
bool last5CharactersAreDigits;
filenameParts[0].right(5).toInt(&last5CharactersAreDigits, 10);
bool extensionIsIMAGE_FORMAT = (filenameParts[1] == imageFormat);
return (filnameHasNoPeriods && contains_tests && last5CharactersAreDigits && extensionIsIMAGE_FORMAT);
}
bool Test::isInExpectedImageFilenameFormat(QString filename) {
return (expectedImageFilenameFormat.match(filename).hasMatch());
}
// For a file named "D_GitHub_hifi-tests_tests_content_entity_zone_create_0.jpg", the test directory is
// D:/GitHub/hifi-tests/tests/content/entity/zone/create
// This method assumes the filename is in the correct format
QString Test::getExpectedImageDestinationDirectory(QString filename) {
QString filenameWithoutExtension = filename.split(".")[0];
QStringList filenameParts = filenameWithoutExtension.split("_");
QString result = filenameParts[0] + ":";
for (int i = 1; i < filenameParts.length() - 1; ++i) {
result += "/" + filenameParts[i];
}
return result;
}
// For a file named "D_GitHub_hifi-tests_tests_content_entity_zone_create_0.jpg", the source directory on GitHub
// is ...tests/content/entity/zone/create
// This is used to create the full URL
// This method assumes the filename is in the correct format
QString Test::getExpectedImagePartialSourceDirectory(QString filename) {
QString filenameWithoutExtension = filename.split(".")[0];
QStringList filenameParts = filenameWithoutExtension.split("_");
// Note that the bottom-most "tests" folder is assumed to be the root
// This is required because the tests folder is named hifi_tests
int i { filenameParts.length() - 1 };
while (i >= 0 && filenameParts[i] != "tests") {
--i;
}
if (i < 0) {
messageBox.critical(0, "Internal error #9", "Bad filename");
exit(-1);
}
QString result = filenameParts[i];
for (int j = i + 1; j < filenameParts.length() - 1; ++j) {
result += "/" + filenameParts[j];
}
return result;
}

View file

@ -23,28 +23,36 @@ class Test {
public:
Test();
void evaluateTests(bool interactiveMode, QProgressBar* progressBar);
void evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar);
void startTestsEvaluation();
void finishTestsEvaluation(bool interactiveMode, QProgressBar* progressBar);
void createRecursiveScript();
void createRecursiveScriptsRecursively();
void createRecursiveScript(QString topLevelDirectory, bool interactiveMode);
void createTest();
void deleteOldSnapshots();
bool compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar);
bool compareImageLists(bool isInteractiveMode, QProgressBar* progressBar);
QStringList createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory);
QStringList createListOfAll_imagesInDirectory(QString imageFormat, QString pathToImageDirectory);
bool isInSnapshotFilenameFormat(QString filename);
bool isInExpectedImageFilenameFormat(QString filename);
bool isInSnapshotFilenameFormat(QString imageFormat, QString filename);
void importTest(QTextStream& textStream, const QString& testPathname);
void appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage);
bool createTestResultsFolderPathIfNeeded(QString directory);
bool createTestResultsFolderPath(QString directory);
void zipAndDeleteTestResultsFolder();
bool isAValidDirectory(QString pathname);
QString getExpectedImageDestinationDirectory(QString filename);
QString getExpectedImagePartialSourceDirectory(QString filename);
void copyJPGtoPNG(QString sourceJPGFullFilename, QString destinationPNGFullFilename);
private:
const QString TEST_FILENAME { "test.js" };
const QString TEST_RESULTS_FOLDER { "TestResults" };
@ -54,16 +62,28 @@ private:
QDir imageDirectory;
QRegularExpression snapshotFilenameFormat;
QRegularExpression expectedImageFilenameFormat;
MismatchWindow mismatchWindow;
ImageComparer imageComparer;
QString testResultsFolderPath { "" };
int index { 1 };
// Expected images are in the format ExpectedImage_dddd.jpg (d == decimal digit)
const int NUM_DIGITS { 5 };
const QString EXPECTED_IMAGE_PREFIX { "ExpectedImage_" };
QString pathToTestResultsDirectory;
QStringList expectedImagesFilenames;
QStringList expectedImagesFullFilenames;
QStringList resultImagesFullFilenames;
// Used for accessing GitHub
const QString user { "NissimHadar" };
const QString branch { "addRecursionToAutotester" };
const QString DATETIME_FORMAT { "yyyy-MM-dd_hh-mm-ss" };
};
#endif // hifi_test_h

View file

@ -10,11 +10,13 @@
#include <QtWidgets/QApplication>
#include "ui/AutoTester.h"
AutoTester* autoTester;
int main(int argc, char *argv[]) {
QApplication application(argc, argv);
AutoTester autoTester;
autoTester.show();
autoTester = new AutoTester();
autoTester->show();
return application.exec();
}

View file

@ -12,32 +12,79 @@
AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) {
ui.setupUi(this);
ui.checkBoxInteractiveMode->setChecked(true);
ui.progressBar->setVisible(false);
test = new Test();
signalMapper = new QSignalMapper();
}
void AutoTester::on_evaluateTestsButton_clicked() {
test.evaluateTests(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar);
}
void AutoTester::on_evaluateTestsRecursivelyButton_clicked() {
test.evaluateTestsRecursively(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar);
test->startTestsEvaluation();
}
void AutoTester::on_createRecursiveScriptButton_clicked() {
test.createRecursiveScript();
test->createRecursiveScript();
}
void AutoTester::on_createRecursiveScriptsRecursivelyButton_clicked() {
test->createRecursiveScriptsRecursively();
}
void AutoTester::on_createTestButton_clicked() {
test.createTest();
}
void AutoTester::on_deleteOldSnapshotsButton_clicked() {
test.deleteOldSnapshots();
test->createTest();
}
void AutoTester::on_closeButton_clicked() {
exit(0);
}
}
void AutoTester::downloadImage(const QUrl& url) {
downloaders.emplace_back(new Downloader(url, this));
connect(downloaders[_index], SIGNAL (downloaded()), signalMapper, SLOT (map()));
signalMapper->setMapping(downloaders[_index], _index);
++_index;
}
void AutoTester::downloadImages(const QStringList& URLs, const QString& directoryName, const QStringList& filenames) {
_directoryName = directoryName;
_filenames = filenames;
_numberOfImagesToDownload = URLs.size();
_numberOfImagesDownloaded = 0;
_index = 0;
ui.progressBar->setMinimum(0);
ui.progressBar->setMaximum(_numberOfImagesToDownload - 1);
ui.progressBar->setValue(0);
ui.progressBar->setVisible(true);
for (int i = 0; i < _numberOfImagesToDownload; ++i) {
QUrl imageURL(URLs[i]);
downloadImage(imageURL);
}
connect(signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int)));
}
void AutoTester::saveImage(int index) {
QPixmap pixmap;
pixmap.loadFromData(downloaders[index]->downloadedData());
QImage image = pixmap.toImage();
image = image.convertToFormat(QImage::Format_ARGB32);
QString fullPathname = _directoryName + "/" + _filenames[index];
image.save(fullPathname, 0, 100);
++_numberOfImagesDownloaded;
if (_numberOfImagesDownloaded == _numberOfImagesToDownload) {
test->finishTestsEvaluation(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar);
} else {
ui.progressBar->setValue(_numberOfImagesDownloaded);
}
}

View file

@ -11,7 +11,10 @@
#define hifi_AutoTester_h
#include <QtWidgets/QMainWindow>
#include <QSignalMapper>
#include "ui_AutoTester.h"
#include "../Downloader.h"
#include "../Test.h"
class AutoTester : public QMainWindow {
@ -19,19 +22,34 @@ class AutoTester : public QMainWindow {
public:
AutoTester(QWidget *parent = Q_NULLPTR);
void downloadImage(const QUrl& url);
void downloadImages(const QStringList& URLs, const QString& directoryName, const QStringList& filenames);
private slots:
void on_evaluateTestsButton_clicked();
void on_evaluateTestsRecursivelyButton_clicked();
void on_createRecursiveScriptButton_clicked();
void on_createRecursiveScriptsRecursivelyButton_clicked();
void on_createTestButton_clicked();
void on_deleteOldSnapshotsButton_clicked();
void on_closeButton_clicked();
void saveImage(int index);
private:
Ui::AutoTesterClass ui;
Test* test;
Test test;
std::vector<Downloader*> downloaders;
// local storage for parameters - folder to store downloaded files in, and a list of their names
QString _directoryName;
QStringList _filenames;
// Used to enable passing a parameter to slots
QSignalMapper* signalMapper;
int _numberOfImagesToDownload;
int _numberOfImagesDownloaded;
int _index;
};
#endif // hifi_AutoTester_h

View file

@ -17,7 +17,7 @@
<widget class="QPushButton" name="closeButton">
<property name="geometry">
<rect>
<x>190</x>
<x>20</x>
<y>300</y>
<width>220</width>
<height>40</height>
@ -30,8 +30,8 @@
<widget class="QPushButton" name="createTestButton">
<property name="geometry">
<rect>
<x>360</x>
<y>130</y>
<x>20</x>
<y>30</y>
<width>220</width>
<height>40</height>
</rect>
@ -44,7 +44,7 @@
<property name="geometry">
<rect>
<x>20</x>
<y>75</y>
<y>135</y>
<width>220</width>
<height>40</height>
</rect>
@ -66,24 +66,11 @@
<string>Create Recursive Script</string>
</property>
</widget>
<widget class="QPushButton" name="evaluateTestsRecursivelyButton">
<property name="geometry">
<rect>
<x>20</x>
<y>130</y>
<width>220</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Evaluate Tests Recursively</string>
</property>
</widget>
<widget class="QCheckBox" name="checkBoxInteractiveMode">
<property name="geometry">
<rect>
<x>23</x>
<y>40</y>
<y>100</y>
<width>131</width>
<height>20</height>
</rect>
@ -108,17 +95,17 @@
<number>24</number>
</property>
</widget>
<widget class="QPushButton" name="deleteOldSnapshotsButton">
<widget class="QPushButton" name="createRecursiveScriptsRecursivelyButton">
<property name="geometry">
<rect>
<x>360</x>
<y>240</y>
<y>140</y>
<width>220</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Delete Old Snapshots</string>
<string>Create Recursive Scripts Recursively</string>
</property>
</widget>
</widget>
@ -145,4 +132,4 @@
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>
</ui>