diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a0d867ade9..4654c311cc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ Contributing git checkout -b new_branch_name ``` 4. Code - * Follow the [coding standard](https://wiki.highfidelity.com/wiki/Coding_Standards) + * Follow the [coding standard](https://docs.highfidelity.com/build-guide/coding-standards) 5. Commit * Use [well formed commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 6. Update your branch diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index 070034d54b..5539d6a0bb 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -91,9 +91,22 @@ void AssignmentClientMonitor::simultaneousWaitOnChildren(int waitMsecs) { } } -void AssignmentClientMonitor::childProcessFinished(qint64 pid) { +void AssignmentClientMonitor::childProcessFinished(qint64 pid, int exitCode, QProcess::ExitStatus exitStatus) { + auto message = "Child process " + QString::number(pid) + " has %1 with exit code " + QString::number(exitCode) + "."; + if (_childProcesses.remove(pid)) { - qDebug() << "Child process" << pid << "has finished. Removed from internal map."; + message.append(" Removed from internal map."); + } else { + message.append(" Could not find process in internal map."); + } + + switch (exitStatus) { + case QProcess::NormalExit: + qDebug() << qPrintable(message.arg("returned")); + break; + case QProcess::CrashExit: + qCritical() << qPrintable(message.arg("crashed")); + break; } } @@ -221,7 +234,9 @@ void AssignmentClientMonitor::spawnChildClient() { auto pid = assignmentClient->processId(); // make sure we hear that this process has finished when it does connect(assignmentClient, static_cast(&QProcess::finished), - this, [this, pid]() { childProcessFinished(pid); }); + this, [this, pid](int exitCode, QProcess::ExitStatus exitStatus) { + childProcessFinished(pid, exitCode, exitStatus); + }); qDebug() << "Spawned a child client with PID" << assignmentClient->processId(); diff --git a/assignment-client/src/AssignmentClientMonitor.h b/assignment-client/src/AssignmentClientMonitor.h index a7f69a559b..8848d503ae 100644 --- a/assignment-client/src/AssignmentClientMonitor.h +++ b/assignment-client/src/AssignmentClientMonitor.h @@ -44,7 +44,7 @@ public: void stopChildProcesses(); private slots: void checkSpares(); - void childProcessFinished(qint64 pid); + void childProcessFinished(qint64 pid, int exitCode, QProcess::ExitStatus exitStatus); void handleChildStatusPacket(QSharedPointer message); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override; diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index 8baf56684a..c384817ff6 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -81,7 +81,11 @@ { "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] }, "when": "Keyboard.RightMouseButton", - "to": "Actions.Yaw" + "to": "Actions.Yaw", + "filters": + [ + { "type": "scale", "scale": 0.6 } + ] }, { "from": "Keyboard.W", "to": "Actions.LONGITUDINAL_FORWARD" }, @@ -102,8 +106,19 @@ { "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" }, { "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" }, - { "from": "Keyboard.MouseMoveUp", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_UP" }, - { "from": "Keyboard.MouseMoveDown", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_DOWN" }, + { "from": "Keyboard.MouseMoveUp", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_UP", + "filters": + [ + { "type": "scale", "scale": 0.6 } + ] + + }, + { "from": "Keyboard.MouseMoveDown", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_DOWN", + "filters": + [ + { "type": "scale", "scale": 0.6 } + ] + }, { "from": "Keyboard.TouchpadDown", "to": "Actions.PITCH_DOWN" }, { "from": "Keyboard.TouchpadUp", "to": "Actions.PITCH_UP" }, diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 47c9af1f57..0256805422 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -506,7 +506,7 @@ Rectangle { } HifiControls.Tree { id: treeView - height: 430 + height: 290 anchors.leftMargin: hifi.dimensions.contentMargin.x + 2 // Extra for border anchors.rightMargin: hifi.dimensions.contentMargin.x + 2 // Extra for border anchors.left: parent.left diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index e4a20a0316..e94325f399 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -21,7 +21,7 @@ TabView { enabled: true property string originalUrl: "" - Rectangle { + Rectangle { color: "#404040" Text { @@ -180,7 +180,7 @@ TabView { WebView { id: entityListToolWebView - url: "../../../../../scripts/system/html/entityList.html" + url: Paths.defaultScripts + "/system/html/entityList.html" anchors.fill: parent enabled: true } @@ -194,7 +194,7 @@ TabView { WebView { id: entityPropertiesWebView - url: "../../../../../scripts/system/html/entityProperties.html" + url: Paths.defaultScripts + "/system/html/entityProperties.html" anchors.fill: parent enabled: true } @@ -208,7 +208,7 @@ TabView { WebView { id: gridControlsWebView - url: "../../../../../scripts/system/html/gridControls.html" + url: Paths.defaultScripts + "/system/html/gridControls.html" anchors.fill: parent enabled: true } @@ -222,7 +222,7 @@ TabView { WebView { id: particleExplorerWebView - url: "../../../../../scripts/system/particle_explorer/particleExplorer.html" + url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html" anchors.fill: parent enabled: true } @@ -293,16 +293,16 @@ TabView { break; case 'list': editTabView.currentIndex = 1; - break; + break; case 'properties': editTabView.currentIndex = 2; - break; + break; case 'grid': editTabView.currentIndex = 3; - break; + break; case 'particle': editTabView.currentIndex = 4; - break; + break; default: console.warn('Attempt to switch to invalid tab:', id); } @@ -310,4 +310,4 @@ TabView { console.warn('Attempt to switch tabs with invalid input:', JSON.stringify(id)); } } -} \ No newline at end of file +} diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f9a4d491c8..b32ef4024e 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -69,8 +69,8 @@ const float MAX_BOOST_SPEED = 0.5f * MAX_WALKING_SPEED; // action motor gets add const float MIN_AVATAR_SPEED = 0.05f; const float MIN_AVATAR_SPEED_SQUARED = MIN_AVATAR_SPEED * MIN_AVATAR_SPEED; // speed is set to zero below this -const float YAW_SPEED_DEFAULT = 120.0f; // degrees/sec -const float PITCH_SPEED_DEFAULT = 90.0f; // degrees/sec +const float YAW_SPEED_DEFAULT = 100.0f; // degrees/sec +const float PITCH_SPEED_DEFAULT = 75.0f; // degrees/sec // TODO: normalize avatar speed for standard avatar size, then scale all motion logic // to properly follow avatar size. diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index acf101159b..bd40de4303 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -81,7 +81,7 @@ private: float _inputVolume { 1.0f }; float _inputLevel { 0.0f }; bool _isMuted { false }; - bool _enableNoiseReduction; + bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index a284e38dac..d02f4d8fcf 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -36,6 +36,21 @@ Setting::Handle& getSetting(bool contextIsHMD, QAudio::Mode mode) { } } +static QString getTargetDevice(bool hmd, QAudio::Mode mode) { + QString deviceName; + auto& setting = getSetting(hmd, mode); + if (setting.isSet()) { + deviceName = setting.get(); + } else if (hmd) { + if (mode == QAudio::AudioInput) { + deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioInDevice(); + } else { // if (_mode == QAudio::AudioOutput) + deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice(); + } + } + return deviceName; +} + QHash AudioDeviceList::_roles { { Qt::DisplayRole, "display" }, { Qt::CheckStateRole, "selected" }, @@ -59,10 +74,15 @@ QVariant AudioDeviceList::data(const QModelIndex& index, int role) const { } } - -void AudioDeviceList::resetDevice(bool contextIsHMD, const QString& device) { +void AudioDeviceList::resetDevice(bool contextIsHMD) { auto client = DependencyManager::get().data(); - auto deviceName = getSetting(contextIsHMD, _mode).get(); + QString deviceName = getTargetDevice(contextIsHMD, _mode); + // FIXME can't use blocking connections here, so we can't determine whether the switch succeeded or not + // We need to have the AudioClient emit signals on switch success / failure + QMetaObject::invokeMethod(client, "switchAudioDevice", + Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName)); + +#if 0 bool switchResult = false; QMetaObject::invokeMethod(client, "switchAudioDevice", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, switchResult), @@ -85,6 +105,7 @@ void AudioDeviceList::resetDevice(bool contextIsHMD, const QString& device) { QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudio::Mode, _mode)); } } +#endif } void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device) { @@ -137,11 +158,8 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) { } void AudioDevices::onContextChanged(const QString& context) { - auto input = getSetting(_contextIsHMD, QAudio::AudioInput).get(); - auto output = getSetting(_contextIsHMD, QAudio::AudioOutput).get(); - - _inputs.resetDevice(_contextIsHMD, input); - _outputs.resetDevice(_contextIsHMD, output); + _inputs.resetDevice(_contextIsHMD); + _outputs.resetDevice(_contextIsHMD); } void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice) { @@ -182,8 +200,16 @@ void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& d void AudioDevices::onDeviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& device) { if (mode == QAudio::AudioInput) { + if (_requestedInputDevice == device) { + onDeviceSelected(QAudio::AudioInput, device, _inputs._selectedDevice); + _requestedInputDevice = QAudioDeviceInfo(); + } _inputs.onDeviceChanged(device); } else { // if (mode == QAudio::AudioOutput) + if (_requestedOutputDevice == device) { + onDeviceSelected(QAudio::AudioOutput, device, _outputs._selectedDevice); + _requestedOutputDevice = QAudioDeviceInfo(); + } _outputs.onDeviceChanged(device); } } @@ -201,28 +227,16 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList(); - bool success = false; + _requestedInputDevice = device; QMetaObject::invokeMethod(client.data(), "switchAudioDevice", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(bool, success), Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(const QAudioDeviceInfo&, device)); - - if (success) { - onDeviceSelected(QAudio::AudioInput, device, _inputs._selectedDevice); - } } void AudioDevices::chooseOutputDevice(const QAudioDeviceInfo& device) { auto client = DependencyManager::get(); - bool success = false; + _requestedOutputDevice = device; QMetaObject::invokeMethod(client.data(), "switchAudioDevice", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(bool, success), Q_ARG(QAudio::Mode, QAudio::AudioOutput), Q_ARG(const QAudioDeviceInfo&, device)); - - if (success) { - onDeviceSelected(QAudio::AudioOutput, device, _outputs._selectedDevice); - } } diff --git a/interface/src/scripting/AudioDevices.h b/interface/src/scripting/AudioDevices.h index a17c577535..3278a53374 100644 --- a/interface/src/scripting/AudioDevices.h +++ b/interface/src/scripting/AudioDevices.h @@ -39,7 +39,7 @@ public: QVariant data(const QModelIndex& index, int role) const override; // reset device to the last selected device in this context, or the default - void resetDevice(bool contextIsHMD, const QString& device); + void resetDevice(bool contextIsHMD); signals: void deviceChanged(const QAudioDeviceInfo& device); @@ -87,8 +87,10 @@ private: AudioDeviceList _inputs { QAudio::AudioInput }; AudioDeviceList _outputs { QAudio::AudioOutput }; + QAudioDeviceInfo _requestedOutputDevice; + QAudioDeviceInfo _requestedInputDevice; - bool& _contextIsHMD; + const bool& _contextIsHMD; }; }; diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp index 443f11fb23..a62fb2270b 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -24,13 +24,14 @@ #include "ScriptHighlighting.h" const int NO_CURRENT_HISTORY_COMMAND = -1; -const int MAX_HISTORY_SIZE = 64; +const int MAX_HISTORY_SIZE = 256; +const QString HISTORY_FILENAME = "JSConsole.history.json"; const QString COMMAND_STYLE = "color: #266a9b;"; const QString RESULT_SUCCESS_STYLE = "color: #677373;"; const QString RESULT_INFO_STYLE = "color: #223bd1;"; -const QString RESULT_WARNING_STYLE = "color: #d13b22;"; +const QString RESULT_WARNING_STYLE = "color: #999922;"; const QString RESULT_ERROR_STYLE = "color: #d13b22;"; const QString GUTTER_PREVIOUS_COMMAND = "<"; @@ -38,14 +39,35 @@ const QString GUTTER_ERROR = "X"; const QString JSConsole::_consoleFileName { "about:console" }; +const QString JSON_KEY = "entries"; +QList _readLines(const QString& filename) { + QFile file(filename); + file.open(QFile::ReadOnly); + auto json = QTextStream(&file).readAll().toUtf8(); + auto root = QJsonDocument::fromJson(json).object(); + // TODO: check root["version"] + return root[JSON_KEY].toVariant().toStringList(); +} + +void _writeLines(const QString& filename, const QList& lines) { + QFile file(filename); + file.open(QFile::WriteOnly); + auto root = QJsonObject(); + root["version"] = 1.0; + root["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + root[JSON_KEY] = QJsonArray::fromStringList(lines); + auto json = QJsonDocument(root).toJson(); + QTextStream(&file) << json; +} + JSConsole::JSConsole(QWidget* parent, ScriptEngine* scriptEngine) : QWidget(parent), _ui(new Ui::Console), _currentCommandInHistory(NO_CURRENT_HISTORY_COMMAND), - _commandHistory(), + _savedHistoryFilename(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + HISTORY_FILENAME), + _commandHistory(_readLines(_savedHistoryFilename)), _ownScriptEngine(scriptEngine == NULL), _scriptEngine(NULL) { - _ui->setupUi(this); _ui->promptTextEdit->setLineWrapMode(QTextEdit::NoWrap); _ui->promptTextEdit->setWordWrapMode(QTextOption::NoWrap); @@ -101,9 +123,12 @@ void JSConsole::setScriptEngine(ScriptEngine* scriptEngine) { } void JSConsole::executeCommand(const QString& command) { - _commandHistory.prepend(command); - if (_commandHistory.length() > MAX_HISTORY_SIZE) { - _commandHistory.removeLast(); + if (_commandHistory.isEmpty() || _commandHistory.constFirst() != command) { + _commandHistory.prepend(command); + if (_commandHistory.length() > MAX_HISTORY_SIZE) { + _commandHistory.removeLast(); + } + _writeLines(_savedHistoryFilename, _commandHistory); } _ui->promptTextEdit->setDisabled(true); @@ -182,7 +207,7 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { // a new QTextBlock isn't created. keyEvent->setModifiers(keyEvent->modifiers() & ~Qt::ShiftModifier); } else { - QString command = _ui->promptTextEdit->toPlainText().trimmed(); + QString command = _ui->promptTextEdit->toPlainText().replace("\r\n","\n").trimmed(); if (!command.isEmpty()) { QTextCursor cursor = _ui->promptTextEdit->textCursor(); diff --git a/interface/src/ui/JSConsole.h b/interface/src/ui/JSConsole.h index 864f847071..59280f65aa 100644 --- a/interface/src/ui/JSConsole.h +++ b/interface/src/ui/JSConsole.h @@ -63,6 +63,7 @@ private: QFutureWatcher _executeWatcher; Ui::Console* _ui; int _currentCommandInHistory; + QString _savedHistoryFilename; QList _commandHistory; // Keeps track if the script engine is created inside the JSConsole bool _ownScriptEngine; diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index ee57e42e77..9f32372a8e 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -27,8 +27,6 @@ #include "AudioSRC.h" #include "AudioHelpers.h" -int audioInjectorPtrMetaTypeId = qRegisterMetaType(); - AbstractAudioInterface* AudioInjector::_localAudioInterface{ nullptr }; AudioInjectorState operator& (AudioInjectorState lhs, AudioInjectorState rhs) { diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index aed51c5f85..fc197f7ba0 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -125,6 +125,4 @@ private: friend class AudioInjectorManager; }; -Q_DECLARE_METATYPE(AudioInjectorPointer) - #endif // hifi_AudioInjector_h diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 417901b9ab..a171f92907 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -123,6 +123,34 @@ glm::vec3 OBJTokenizer::getVec3() { } return v; } +bool OBJTokenizer::getVertex(glm::vec3& vertex, glm::vec3& vertexColor) { + // Used for vertices which may also have a vertex color (RGB [0,1]) to follow. + // NOTE: Returns true if there is a vertex color. + auto x = getFloat(); // N.B.: getFloat() has side-effect + auto y = getFloat(); // And order of arguments is different on Windows/Linux. + auto z = getFloat(); + vertex = glm::vec3(x, y, z); + + auto r = 1.0f, g = 1.0f, b = 1.0f; + bool hasVertexColor = false; + if (isNextTokenFloat()) { + // If there's another float it's one of two things: a W component or an R component. The standard OBJ spec + // doesn't output a W component, so we're making the assumption that if a float follows (unless it's + // only a single value) that it's a vertex color. + r = getFloat(); + if (isNextTokenFloat()) { + // Safe to assume the following values are the green/blue components. + g = getFloat(); + b = getFloat(); + + hasVertexColor = true; + } + + vertexColor = glm::vec3(r, g, b); + } + + return hasVertexColor; +} glm::vec2 OBJTokenizer::getVec2() { float uCoord = getFloat(); float vCoord = 1.0f - getFloat(); @@ -140,7 +168,9 @@ void setMeshPartDefaults(FBXMeshPart& meshPart, QString materialID) { } // OBJFace -bool OBJFace::add(const QByteArray& vertexIndex, const QByteArray& textureIndex, const QByteArray& normalIndex, const QVector& vertices) { +// NOTE (trent, 7/20/17): The vertexColors vector being passed-in isn't necessary here, but I'm just +// pairing it with the vertices vector for consistency. +bool OBJFace::add(const QByteArray& vertexIndex, const QByteArray& textureIndex, const QByteArray& normalIndex, const QVector& vertices, const QVector& vertexColors) { bool ok; int index = vertexIndex.toInt(&ok); if (!ok) { @@ -382,7 +412,14 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi #endif } } else if (token == "v") { - vertices.append(tokenizer.getVec3()); + glm::vec3 vertex, vertexColor; + + bool hasVertexColor = tokenizer.getVertex(vertex, vertexColor); + vertices.append(vertex); + + if(hasVertexColor) { + vertexColors.append(vertexColor); + } } else if (token == "vn") { normals.append(tokenizer.getVec3()); } else if (token == "vt") { @@ -410,7 +447,8 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi assert(parts.count() >= 1); assert(parts.count() <= 3); const QByteArray noData {}; - face.add(parts[0], (parts.count() > 1) ? parts[1] : noData, (parts.count() > 2) ? parts[2] : noData, vertices); + face.add(parts[0], (parts.count() > 1) ? parts[1] : noData, (parts.count() > 2) ? parts[2] : noData, + vertices, vertexColors); face.groupName = currentGroup; face.materialName = currentMaterialName; } @@ -540,6 +578,15 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, glm::vec3 v1 = checked_at(vertices, face.vertexIndices[1]); glm::vec3 v2 = checked_at(vertices, face.vertexIndices[2]); + glm::vec3 vc0, vc1, vc2; + bool hasVertexColors = (vertexColors.size() > 0); + if (hasVertexColors) { + // If there are any vertex colors, it's safe to assume all meshes had them exported. + vc0 = checked_at(vertexColors, face.vertexIndices[0]); + vc1 = checked_at(vertexColors, face.vertexIndices[1]); + vc2 = checked_at(vertexColors, face.vertexIndices[2]); + } + // Scale the vertices if the OBJ file scale is specified as non-one. if (scaleGuess != 1.0f) { v0 *= scaleGuess; @@ -555,6 +602,13 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, meshPart.triangleIndices.append(mesh.vertices.count()); mesh.vertices << v2; + if (hasVertexColors) { + // Add vertex colors. + mesh.colors << vc0; + mesh.colors << vc1; + mesh.colors << vc2; + } + glm::vec3 n0, n1, n2; if (face.normalIndices.count()) { n0 = checked_at(normals, face.normalIndices[0]); @@ -690,6 +744,7 @@ void fbxDebugDump(const FBXGeometry& fbxgeo) { qCDebug(modelformat) << " meshes.count() =" << fbxgeo.meshes.count(); foreach (FBXMesh mesh, fbxgeo.meshes) { qCDebug(modelformat) << " vertices.count() =" << mesh.vertices.count(); + qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); qCDebug(modelformat) << " normals.count() =" << mesh.normals.count(); /*if (mesh.normals.count() == mesh.vertices.count()) { for (int i = 0; i < mesh.normals.count(); i++) { diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 18a4b89f1e..9a32871590 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -20,6 +20,7 @@ public: void ungetChar(char ch) { _device->ungetChar(ch); } const QString getComment() const { return _comment; } glm::vec3 getVec3(); + bool getVertex(glm::vec3& vertex, glm::vec3& vertexColor); glm::vec2 getVec2(); float getFloat() { return std::stof((nextToken() != OBJTokenizer::DATUM_TOKEN) ? nullptr : getDatum().data()); } @@ -38,7 +39,8 @@ public: QString groupName; // We don't make use of hierarchical structure, but it can be preserved for debugging and future use. QString materialName; // Add one more set of vertex data. Answers true if successful - bool add(const QByteArray& vertexIndex, const QByteArray& textureIndex, const QByteArray& normalIndex, const QVector& vertices); + bool add(const QByteArray& vertexIndex, const QByteArray& textureIndex, const QByteArray& normalIndex, + const QVector& vertices, const QVector& vertexColors); // Return a set of one or more OBJFaces from this one, in which each is just a triangle. // Even though FBXMeshPart can handle quads, it would be messy to try to keep track of mixed-size faces, so we treat everything as triangles. QVector triangulate(); @@ -65,7 +67,8 @@ class OBJReader: public QObject { // QObject so we can make network requests. Q_OBJECT public: typedef QVector FaceGroup; - QVector vertices; // all that we ever encounter while reading + QVector vertices; + QVector vertexColors; QVector textureUVs; QVector normals; QVector faceGroups; diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index 14eb81dd9a..577c215c9e 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -13,6 +13,8 @@ #define hifi_PathUtils_h #include +#include + #include "DependencyManager.h" /**jsdoc @@ -24,6 +26,7 @@ class PathUtils : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY Q_PROPERTY(QString resources READ resourcesPath) + Q_PROPERTY(QUrl defaultScripts READ defaultScriptsLocation) public: static const QString& resourcesPath(); diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 58b8aead45..99bdfc4d90 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -47,6 +47,7 @@ extern "C" FILE * __cdecl __iob_func(void) { #include #include #include +#include #include #include #include @@ -1077,14 +1078,20 @@ void setMaxCores(uint8_t maxCores) { #endif } -#ifdef Q_OS_WIN -VOID CALLBACK parentDiedCallback(PVOID lpParameter, BOOLEAN timerOrWaitFired) { - if (!timerOrWaitFired && qApp) { +void quitWithParentProcess() { + if (qApp) { qDebug() << "Parent process died, quitting"; qApp->quit(); } } +#ifdef Q_OS_WIN +VOID CALLBACK parentDiedCallback(PVOID lpParameter, BOOLEAN timerOrWaitFired) { + if (!timerOrWaitFired) { + quitWithParentProcess(); + } +} + void watchParentProcess(int parentPID) { DWORD processID = parentPID; HANDLE procHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID); @@ -1092,8 +1099,17 @@ void watchParentProcess(int parentPID) { HANDLE newHandle; RegisterWaitForSingleObject(&newHandle, procHandle, parentDiedCallback, NULL, INFINITE, WT_EXECUTEONLYONCE); } -#else +#elif defined(Q_OS_MAC) || defined(Q_OS_LINUX) void watchParentProcess(int parentPID) { - qWarning() << "Parent PID option not implemented on this plateform"; + auto timer = new QTimer(qApp); + timer->setInterval(MSECS_PER_SECOND); + QObject::connect(timer, &QTimer::timeout, qApp, [parentPID]() { + auto ppid = getppid(); + if (parentPID != ppid) { + // If the PPID changed, then that means our parent process died. + quitWithParentProcess(); + } + }); + timer->start(); } -#endif // Q_OS_WIN \ No newline at end of file +#endif diff --git a/libraries/shared/src/shared/FileCache.cpp b/libraries/shared/src/shared/FileCache.cpp index 695336847e..9957c6bfc6 100644 --- a/libraries/shared/src/shared/FileCache.cpp +++ b/libraries/shared/src/shared/FileCache.cpp @@ -258,7 +258,8 @@ namespace cache { }; } -void FileCache::eject(const FilePointer& file) { +// Take file pointer by value to insure it doesn't get destructed during the "erase()" calls +void FileCache::eject(FilePointer file) { file->_locked = false; const auto& length = file->getLength(); const auto& key = file->getKey(); diff --git a/libraries/shared/src/shared/FileCache.h b/libraries/shared/src/shared/FileCache.h index 1580674ca0..94fa57457a 100644 --- a/libraries/shared/src/shared/FileCache.h +++ b/libraries/shared/src/shared/FileCache.h @@ -119,7 +119,7 @@ private: void clean(); void clear(); // Remove a file from the cache - void eject(const FilePointer& file); + void eject(FilePointer file); size_t getOverbudgetAmount() const; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 76f290f17e..6ff5e46cea 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -229,7 +229,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { } else { removeButtonsFromToolbar(); addButtonsToHomeScreen(); - emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL)); // destroy desktop window if (_desktopWindow) { @@ -237,6 +236,8 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { _desktopWindow = nullptr; } } + loadHomeScreen(true); + emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL)); } static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 6bc93ac39f..a9a96c8e3c 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1034,9 +1034,18 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp) { function getControllerJointIndex(hand) { if (HMD.isHandControllerAvailable()) { - return MyAvatar.getJointIndex(hand === RIGHT_HAND ? - "_CONTROLLER_RIGHTHAND" : - "_CONTROLLER_LEFTHAND"); + var controllerJointIndex = -1; + if (Camera.mode === "first person") { + controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND"); + } else if (Camera.mode === "third person") { + controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); + } + + return controllerJointIndex; } return MyAvatar.getJointIndex("Head"); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 777ef54085..70594d8f1e 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1074,7 +1074,7 @@ function loaded() { elDimensionsZ.addEventListener('change', dimensionsChangeFunction); elParentID.addEventListener('change', createEmitTextPropertyUpdateFunction('parentID')); - elParentJointIndex.addEventListener('change', createEmitNumberPropertyUpdateFunction('parentJointIndex')); + elParentJointIndex.addEventListener('change', createEmitNumberPropertyUpdateFunction('parentJointIndex', 0)); var registrationChangeFunction = createEmitVec3PropertyUpdateFunction( 'registrationPoint', elRegistrationX, elRegistrationY, elRegistrationZ); diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 725803f824..2d1853fae2 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1560,7 +1560,6 @@ SelectionDisplay = (function() { visible: rotationOverlaysVisible }); - // TODO: we have not implemented the rotating handle/controls yet... so for now, these handles are hidden Overlays.editOverlay(yawHandle, { visible: rotateHandlesVisible, position: yawCorner, @@ -3615,24 +3614,21 @@ SelectionDisplay = (function() { onMove: function(event) { var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { - ignoreRayIntersection: true, visible: false }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { - ignoreRayIntersection: true, visible: false }); - Overlays.editOverlay(rotateOverlayTarget, { - ignoreRayIntersection: false - }); - var result = Overlays.findRayIntersection(pickRay); + var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); if (result.intersects) { var center = yawCenter; var zero = yawZero; + // TODO: these vectors are backwards to their names, doesn't matter for this use case (inverted vectors still give same angle) var centerToZero = Vec3.subtract(center, zero); var centerToIntersect = Vec3.subtract(center, result.intersection); + // TODO: orientedAngle wants normalized centerToZero and centerToIntersect var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); var distanceFromCenter = Vec3.distance(center, result.intersection); var snapToInner = distanceFromCenter < innerRadius; @@ -3785,17 +3781,12 @@ SelectionDisplay = (function() { onMove: function(event) { var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { - ignoreRayIntersection: true, visible: false }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { - ignoreRayIntersection: true, visible: false }); - Overlays.editOverlay(rotateOverlayTarget, { - ignoreRayIntersection: false - }); - var result = Overlays.findRayIntersection(pickRay); + var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); if (result.intersects) { var properties = Entities.getEntityProperties(selectionManager.selections[0]); @@ -3947,17 +3938,12 @@ SelectionDisplay = (function() { onMove: function(event) { var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { - ignoreRayIntersection: true, visible: false }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { - ignoreRayIntersection: true, visible: false }); - Overlays.editOverlay(rotateOverlayTarget, { - ignoreRayIntersection: false - }); - var result = Overlays.findRayIntersection(pickRay); + var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); if (result.intersects) { var properties = Entities.getEntityProperties(selectionManager.selections[0]); @@ -4074,21 +4060,8 @@ SelectionDisplay = (function() { return false; } - // before we do a ray test for grabbers, disable the ray intersection for our selection box - Overlays.editOverlay(selectionBox, { - ignoreRayIntersection: true - }); - Overlays.editOverlay(yawHandle, { - ignoreRayIntersection: true - }); - Overlays.editOverlay(pitchHandle, { - ignoreRayIntersection: true - }); - Overlays.editOverlay(rollHandle, { - ignoreRayIntersection: true - }); - - result = Overlays.findRayIntersection(pickRay); + // ignore ray intersection for our selection box and yaw/pitch/roll + result = Overlays.findRayIntersection(pickRay, true, null, [ yawHandle, pitchHandle, rollHandle, selectionBox ] ); if (result.intersects) { if (wantDebug) { print("something intersects... "); @@ -4191,17 +4164,8 @@ SelectionDisplay = (function() { } - // After testing our stretch handles, then check out rotate handles - Overlays.editOverlay(yawHandle, { - ignoreRayIntersection: false - }); - Overlays.editOverlay(pitchHandle, { - ignoreRayIntersection: false - }); - Overlays.editOverlay(rollHandle, { - ignoreRayIntersection: false - }); - var result = Overlays.findRayIntersection(pickRay); + // Only intersect versus yaw/pitch/roll. + var result = Overlays.findRayIntersection(pickRay, true, [ yawHandle, pitchHandle, rollHandle ] ); var overlayOrientation; var overlayCenter; @@ -4306,6 +4270,7 @@ SelectionDisplay = (function() { }); + // TODO: these three duplicate prior three, remove them. Overlays.editOverlay(yawHandle, { visible: false }); @@ -4402,10 +4367,8 @@ SelectionDisplay = (function() { } if (!somethingClicked) { - Overlays.editOverlay(selectionBox, { - ignoreRayIntersection: false - }); - var result = Overlays.findRayIntersection(pickRay); + // Only intersect versus selectionBox. + var result = Overlays.findRayIntersection(pickRay, true, [selectionBox]); if (result.intersects) { switch (result.overlayID) { case selectionBox: diff --git a/scripts/tutorials/entity_scripts/sit.js b/scripts/tutorials/entity_scripts/sit.js index 3d3bc10fb1..70456ea493 100644 --- a/scripts/tutorials/entity_scripts/sit.js +++ b/scripts/tutorials/entity_scripts/sit.js @@ -12,9 +12,9 @@ Script.include("/~/system/libraries/utils.js"); if (!String.prototype.startsWith) { String.prototype.startsWith = function(searchString, position){ - position = position || 0; - return this.substr(position, searchString.length) === searchString; - }; + position = position || 0; + return this.substr(position, searchString.length) === searchString; + }; } var SETTING_KEY = "com.highfidelity.avatar.isSitting"; @@ -122,10 +122,20 @@ this.rolesToOverride = function() { return MyAvatar.getAnimationRoles().filter(function(role) { - return role === "fly" || role.startsWith("inAir"); + return !(role.startsWith("right") || role.startsWith("left")); }); } + // Handler for user changing the avatar model while sitting. There's currently an issue with changing avatar models while override role animations are applied, + // so to avoid that problem, re-apply the role overrides once the model has finished changing. + this.modelURLChangeFinished = function () { + print("Sitter's model has FINISHED changing. Reapply anim role overrides."); + var roles = this.rolesToOverride(); + for (i in roles) { + MyAvatar.overrideRoleAnimation(roles[i], ANIMATION_URL, ANIMATION_FPS, true, ANIMATION_FIRST_FRAME, ANIMATION_LAST_FRAME); + } + } + this.sitDown = function() { if (this.checkSeatForAvatar()) { print("Someone is already sitting in that chair."); @@ -164,12 +174,14 @@ return { headType: 0 }; }, ["headType"]); Script.update.connect(this, this.update); + MyAvatar.onLoadComplete.connect(this, this.modelURLChangeFinished); } this.standUp = function() { print("Standing up (" + this.entityID + ")"); MyAvatar.removeAnimationStateHandler(this.animStateHandlerID); Script.update.disconnect(this, this.update); + MyAvatar.onLoadComplete.disconnect(this, this.modelURLChangeFinished); if (MyAvatar.sessionUUID === this.getSeatUser()) { this.setSeatUser(null); @@ -331,7 +343,7 @@ } this.cleanupOverlay(); } - + this.clickDownOnEntity = function (id, event) { if (isInEditMode()) { return;