diff --git a/CMakeLists.txt b/CMakeLists.txt index 406fb5aeb1..3c90256134 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -208,9 +208,10 @@ foreach(CUSTOM_MACRO ${HIFI_CUSTOM_MACROS}) include(${CUSTOM_MACRO}) endforeach() -file(GLOB_RECURSE JS_SRC scripts/*.js) +file(GLOB_RECURSE JS_SRC scripts/*.js unpublishedScripts/*.js) add_custom_target(js SOURCES ${JS_SRC}) GroupSources("scripts") +GroupSources("unpublishedScripts") if (UNIX) install( diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 469ca9f178..47836727fe 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -592,6 +592,7 @@ void Agent::setIsAvatar(bool isAvatar) { void Agent::sendAvatarIdentityPacket() { if (_isAvatar) { auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>(); + scriptedAvatar->markIdentityDataChanged(); scriptedAvatar->sendIdentityPacket(); } } diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index b95c429b2d..bb03a6ec93 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -38,13 +38,14 @@ #include "AudioMixer.h" static const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f; // attenuation = -6dB * log2(distance) +static const int DISABLE_STATIC_JITTER_FRAMES = -1; static const float DEFAULT_NOISE_MUTING_THRESHOLD = 1.0f; static const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer"; static const QString AUDIO_ENV_GROUP_KEY = "audio_env"; static const QString AUDIO_BUFFER_GROUP_KEY = "audio_buffer"; static const QString AUDIO_THREADING_GROUP_KEY = "audio_threading"; -int AudioMixer::_numStaticJitterFrames{ -1 }; +int AudioMixer::_numStaticJitterFrames{ DISABLE_STATIC_JITTER_FRAMES }; float AudioMixer::_noiseMutingThreshold{ DEFAULT_NOISE_MUTING_THRESHOLD }; float AudioMixer::_attenuationPerDoublingInDistance{ DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE }; std::map<QString, std::shared_ptr<CodecPlugin>> AudioMixer::_availableCodecs{ }; @@ -56,7 +57,12 @@ QVector<AudioMixer::ReverbSettings> AudioMixer::_zoneReverbSettings; AudioMixer::AudioMixer(ReceivedMessage& message) : ThreadedAssignment(message) { + // Always clear settings first + // This prevents previous assignment settings from sticking around + clearDomainSettings(); + // hash the available codecs (on the mixer) + _availableCodecs.clear(); // Make sure struct is clean auto codecPlugins = PluginManager::getInstance()->getCodecPlugins(); std::for_each(codecPlugins.cbegin(), codecPlugins.cend(), [&](const CodecPluginPointer& codec) { @@ -232,7 +238,7 @@ void AudioMixer::sendStatsPacket() { } // general stats - statsObject["useDynamicJitterBuffers"] = _numStaticJitterFrames == -1; + statsObject["useDynamicJitterBuffers"] = _numStaticJitterFrames == DISABLE_STATIC_JITTER_FRAMES; statsObject["threads"] = _slavePool.numThreads(); @@ -490,6 +496,16 @@ int AudioMixer::prepareFrame(const SharedNodePointer& node, unsigned int frame) return data->checkBuffersBeforeFrameSend(); } +void AudioMixer::clearDomainSettings() { + _numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES; + _attenuationPerDoublingInDistance = DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE; + _noiseMutingThreshold = DEFAULT_NOISE_MUTING_THRESHOLD; + _codecPreferenceOrder.clear(); + _audioZones.clear(); + _zoneSettings.clear(); + _zoneReverbSettings.clear(); +} + void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) { qDebug() << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled"); @@ -525,7 +541,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) { qDebug() << "Static desired jitter buffer frames:" << _numStaticJitterFrames; } else { qDebug() << "Disabling dynamic jitter buffers."; - _numStaticJitterFrames = -1; + _numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES; } // check for deprecated audio settings diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 0641c04a6c..18e754016e 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -79,6 +79,7 @@ private: QString percentageForMixStats(int counter); void parseSettingsObject(const QJsonObject& settingsObject); + void clearDomainSettings(); float _trailingMixRatio { 0.0f }; float _throttlingRatio { 0.0f }; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 15a7f50fa3..4d80bc7d17 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -16,7 +16,14 @@ #include "AvatarMixerClientData.h" +AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) : + NodeData(nodeID) +{ + _currentViewFrustum.invalidate(); + // in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID + _avatar->setID(nodeID); +} void AvatarMixerClientData::queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) { if (!_packetQueue.node) { diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 76519466b5..c905b10251 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -36,7 +36,7 @@ const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps"; class AvatarMixerClientData : public NodeData { Q_OBJECT public: - AvatarMixerClientData(const QUuid& nodeID = QUuid()) : NodeData(nodeID) { _currentViewFrustum.invalidate(); } + AvatarMixerClientData(const QUuid& nodeID = QUuid()); virtual ~AvatarMixerClientData() {} using HRCTime = p_high_resolution_clock::time_point; diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index b18599d8a9..f88e322d99 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -68,7 +68,7 @@ "typeVar": "rightHandType", "weightVar": "rightHandWeight", "weight": 1.0, - "flexCoefficients": [1, 0.5, 0.5, 0.25, 0.1, 0.05, 0.01, 0.0, 0.0] + "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0] }, { "jointName": "LeftHand", @@ -77,7 +77,7 @@ "typeVar": "leftHandType", "weightVar": "leftHandWeight", "weight": 1.0, - "flexCoefficients": [1, 0.5, 0.5, 0.25, 0.1, 0.05, 0.01, 0.0, 0.0] + "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0] }, { "jointName": "RightFoot", @@ -104,7 +104,7 @@ "typeVar": "spine2Type", "weightVar": "spine2Weight", "weight": 1.0, - "flexCoefficients": [0.45, 0.45] + "flexCoefficients": [1.0, 0.5, 0.5] }, { "jointName": "Head", @@ -113,7 +113,7 @@ "typeVar": "headType", "weightVar": "headWeight", "weight": 4.0, - "flexCoefficients": [1, 0.5, 0.5, 0.5, 0.5] + "flexCoefficients": [1, 0.05, 0.25, 0.25, 0.25] } ] }, diff --git a/interface/resources/icons/loader-snake-64-w.gif b/interface/resources/icons/loader-snake-64-w.gif new file mode 100644 index 0000000000..e6594ab0a2 Binary files /dev/null and b/interface/resources/icons/loader-snake-64-w.gif differ diff --git a/interface/resources/images/Default-Sky-9-cubemap.ktx b/interface/resources/images/Default-Sky-9-cubemap.ktx index 476d381a8c..70f495c3ab 100644 Binary files a/interface/resources/images/Default-Sky-9-cubemap.ktx and b/interface/resources/images/Default-Sky-9-cubemap.ktx differ diff --git a/interface/resources/qml/TabletBrowser.qml b/interface/resources/qml/TabletBrowser.qml index 74318a165e..ee4d05a701 100644 --- a/interface/resources/qml/TabletBrowser.qml +++ b/interface/resources/qml/TabletBrowser.qml @@ -4,6 +4,7 @@ import QtWebChannel 1.0 import QtWebEngine 1.2 import "controls" +import "controls-uit" as HifiControls import "styles" as HifiStyles import "styles-uit" import "windows" @@ -117,6 +118,8 @@ Item { onNewViewRequested: { request.openIn(webView); } + + HifiControls.WebSpinner { } } Keys.onPressed: { diff --git a/interface/resources/qml/controls-uit/BaseWebView.qml b/interface/resources/qml/controls-uit/BaseWebView.qml index 9c22a8ff8c..670aea71aa 100644 --- a/interface/resources/qml/controls-uit/BaseWebView.qml +++ b/interface/resources/qml/controls-uit/BaseWebView.qml @@ -36,4 +36,6 @@ WebEngineView { } } } + + WebSpinner { } } diff --git a/interface/resources/qml/controls-uit/WebSpinner.qml b/interface/resources/qml/controls-uit/WebSpinner.qml new file mode 100644 index 0000000000..6323bff1a7 --- /dev/null +++ b/interface/resources/qml/controls-uit/WebSpinner.qml @@ -0,0 +1,21 @@ +// +// WebSpinner.qml +// +// Created by David Rowe on 23 May 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +AnimatedImage { + source: "../../icons/loader-snake-64-w.gif" + visible: parent.loading && /^(http.*|)$/i.test(parent.url.toString()) + z: 10000 + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } +} diff --git a/interface/resources/qml/controls/TabletWebScreen.qml b/interface/resources/qml/controls/TabletWebScreen.qml index fec91046d8..93ded724a1 100644 --- a/interface/resources/qml/controls/TabletWebScreen.qml +++ b/interface/resources/qml/controls/TabletWebScreen.qml @@ -116,6 +116,8 @@ Item { tabletRoot.openBrowserWindow(request, profile); } } + + HiFiControls.WebSpinner { } } HiFiControls.Keyboard { diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index d939e088a8..215ac68ac0 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -238,6 +238,8 @@ Item { onNewViewRequested: { request.openIn(webview); } + + HiFiControls.WebSpinner { } } HiFiControls.Keyboard { diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 04ff731a25..06766fa6ef 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -116,6 +116,8 @@ Item { tabletRoot.openBrowserWindow(request, profile); } } + + HiFiControls.WebSpinner { } } HiFiControls.Keyboard { diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml index 0e98f79216..b6f906ffc2 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml @@ -181,10 +181,10 @@ Item { HifiControls.SpinBox { id: scaleSpinner; anchors { left: parent.left; right: parent.right; bottom: parent.bottom; } - decimals: 1; - minimumValue: 0.1 + decimals: 2; + minimumValue: 0.01 maximumValue: 10 - stepSize: 0.1; + stepSize: 0.05; value: attachment ? attachment.scale : 1.0 colorScheme: hifi.colorSchemes.dark onValueChanged: { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1d31a84ed4..ae490c05e7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -431,7 +431,7 @@ static const QString STATE_ADVANCED_MOVEMENT_CONTROLS = "AdvancedMovement"; static const QString STATE_GROUNDED = "Grounded"; static const QString STATE_NAV_FOCUSED = "NavigationFocused"; -bool setupEssentials(int& argc, char** argv) { +bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { const char** constArgv = const_cast<const char**>(argv); const char* portStr = getCmdOption(argc, constArgv, "--listenPort"); const int listenPort = portStr ? atoi(portStr) : INVALID_PORT; @@ -458,7 +458,7 @@ bool setupEssentials(int& argc, char** argv) { static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset"; bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET); - bool previousSessionCrashed = CrashHandler::checkForResetSettings(suppressPrompt); + bool previousSessionCrashed = CrashHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt); DependencyManager::registerInheritance<LimitedNodeList, NodeList>(); DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>(); @@ -563,11 +563,11 @@ const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true; const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false; const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false; -Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : +Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runningMarkerExisted) : QApplication(argc, argv), _window(new MainWindow(desktop())), _sessionRunTimer(startupTimer), - _previousSessionCrashed(setupEssentials(argc, argv)), + _previousSessionCrashed(setupEssentials(argc, argv, runningMarkerExisted)), _undoStackScriptingInterface(&_undoStack), _entitySimulation(new PhysicalEntitySimulation()), _physicsEngine(new PhysicsEngine(Vectors::ZERO)), @@ -3976,7 +3976,7 @@ void Application::updateMyAvatarLookAtPosition() { if (faceAngle < MAXIMUM_FACE_ANGLE) { // Randomly look back and forth between look targets eyeContactTarget target = Menu::getInstance()->isOptionChecked(MenuOption::FixGaze) ? - LEFT_EYE : myAvatar->getEyeContactTarget(); + LEFT_EYE : myAvatar->getEyeContactTarget(); switch (target) { case LEFT_EYE: lookAtSpot = lookingAtHead->getLeftEyePosition(); @@ -6428,7 +6428,7 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa if (!includeAnimated) { // Tell the dependency manager that the capture of the still snapshot has taken place. emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify); - } else { + } else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) { // Get an animated GIF snapshot and save it SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>()); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 5cf3580c09..d9dc3f389f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -136,7 +136,7 @@ public: static void initPlugins(const QStringList& arguments); static void shutdownPlugins(); - Application(int& argc, char** argv, QElapsedTimer& startup_time); + Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runningMarkerExisted); ~Application(); void postLambdaEvent(std::function<void()> f) override; diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp index b254cd58de..8081dd3ffc 100644 --- a/interface/src/CrashHandler.cpp +++ b/interface/src/CrashHandler.cpp @@ -28,7 +28,7 @@ #include <RunningMarker.h> -bool CrashHandler::checkForResetSettings(bool suppressPrompt) { +bool CrashHandler::checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt) { Settings settings; settings.beginGroup("Developer"); QVariant displayCrashOptions = settings.value(MenuOption::DisplayCrashOptions); @@ -39,9 +39,6 @@ bool CrashHandler::checkForResetSettings(bool suppressPrompt) { // If option does not exist in Interface.ini so assume default behavior. bool displaySettingsResetOnCrash = !displayCrashOptions.isValid() || displayCrashOptions.toBool(); - QFile runningMarkerFile(RunningMarker::getMarkerFilePath(RUNNING_MARKER_FILENAME)); - bool wasLikelyCrash = runningMarkerFile.exists(); - if (suppressPrompt) { return wasLikelyCrash; } diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h index 308cac3411..da2e1575db 100644 --- a/interface/src/CrashHandler.h +++ b/interface/src/CrashHandler.h @@ -17,7 +17,7 @@ class CrashHandler { public: - static bool checkForResetSettings(bool suppressPrompt = false); + static bool checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt = false); private: enum Action { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f5f248602d..8461b1d42a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -525,6 +525,8 @@ Menu::Menu() { avatar.get(), SLOT(setEnableDebugDrawIKTargets(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderIKConstraints, 0, false, avatar.get(), SLOT(setEnableDebugDrawIKConstraints(bool))); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderIKChains, 0, false, + avatar.get(), SLOT(setEnableDebugDrawIKChains(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ActionMotorControl, Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar.get(), SLOT(updateMotionBehaviorFromMenu()), diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b1f69a28d3..b6d72f5446 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -162,6 +162,7 @@ namespace MenuOption { const QString RenderSensorToWorldMatrix = "Show SensorToWorld Matrix"; const QString RenderIKTargets = "Show IK Targets"; const QString RenderIKConstraints = "Show IK Constraints"; + const QString RenderIKChains = "Show IK Chains"; const QString ResetAvatarSize = "Reset Avatar Size"; const QString ResetSensors = "Reset Sensors"; const QString RunningScripts = "Running Scripts..."; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index d47e4cfd10..20b3949bc6 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -52,7 +52,7 @@ const QUuid MY_AVATAR_KEY; // NULL key AvatarManager::AvatarManager(QObject* parent) : _avatarsToFade(), - _myAvatar(std::make_shared<MyAvatar>(qApp->thread(), std::make_shared<Rig>())) + _myAvatar(std::make_shared<MyAvatar>(qApp->thread())) { // register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar qRegisterMetaType<QWeakPointer<Node> >("NodeWeakPointer"); @@ -300,7 +300,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { } AvatarSharedPointer AvatarManager::newSharedAvatar() { - return std::make_shared<OtherAvatar>(qApp->thread(), std::make_shared<Rig>()); + return std::make_shared<OtherAvatar>(qApp->thread()); } void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9cf8e7747b..b4e418b6a2 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -98,8 +98,8 @@ static const glm::quat DEFAULT_AVATAR_LEFTFOOT_ROT { -0.40167322754859924f, 0.91 static const glm::vec3 DEFAULT_AVATAR_RIGHTFOOT_POS { 0.08f, -0.96f, 0.029f }; static const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { -0.4016716778278351f, 0.9154615998268127f, 0.0053307069465518f, 0.023696165531873703f }; -MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : - Avatar(thread, rig), +MyAvatar::MyAvatar(QThread* thread) : + Avatar(thread), _yawSpeed(YAW_SPEED_DEFAULT), _pitchSpeed(PITCH_SPEED_DEFAULT), _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), @@ -120,7 +120,6 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : _goToPending(false), _goToPosition(), _goToOrientation(), - _rig(rig), _prevShouldDrawHead(true), _audioListenerMode(FROM_HEAD), _hmdAtRestDetector(glm::vec3(0), glm::quat()) @@ -129,7 +128,7 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : // give the pointer to our head to inherited _headData variable from AvatarData _headData = new MyHead(this); - _skeletonModel = std::make_shared<MySkeletonModel>(this, nullptr, rig); + _skeletonModel = std::make_shared<MySkeletonModel>(this, nullptr); connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); @@ -180,9 +179,7 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : auto audioIO = DependencyManager::get<AudioClient>(); audioIO->setIsPlayingBackRecording(isPlaying); - if (_rig) { - _rig->setEnableAnimations(!isPlaying); - } + _skeletonModel->getRig().setEnableAnimations(!isPlaying); }); connect(recorder.data(), &Recorder::recordingStateChanged, [=] { @@ -233,12 +230,12 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : } auto jointData = dummyAvatar.getRawJointData(); - if (jointData.length() > 0 && _rig) { - _rig->copyJointsFromJointData(jointData); + if (jointData.length() > 0) { + _skeletonModel->getRig().copyJointsFromJointData(jointData); } }); - connect(rig.get(), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete())); + connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete())); _characterController.setDensity(_density); } @@ -355,9 +352,7 @@ void MyAvatar::clearIKJointLimitHistory() { return; } - if (_rig) { - _rig->clearIKJointLimitHistory(); - } + _skeletonModel->getRig().clearIKJointLimitHistory(); } void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { @@ -528,11 +523,9 @@ void MyAvatar::simulate(float deltaTime) { { PerformanceTimer perfTimer("skeleton"); - if (_rig) { - _rig->setEnableDebugDrawIKTargets(_enableDebugDrawIKTargets); - _rig->setEnableDebugDrawIKConstraints(_enableDebugDrawIKConstraints); - } - + _skeletonModel->getRig().setEnableDebugDrawIKTargets(_enableDebugDrawIKTargets); + _skeletonModel->getRig().setEnableDebugDrawIKConstraints(_enableDebugDrawIKConstraints); + _skeletonModel->getRig().setEnableDebugDrawIKChains(_enableDebugDrawIKChains); _skeletonModel->simulate(deltaTime); } @@ -550,7 +543,7 @@ void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("joints"); // copy out the skeleton joints from the model if (_rigEnabled) { - _rig->copyJointsIntoJointData(_jointData); + _skeletonModel->getRig().copyJointsIntoJointData(_jointData); } } @@ -795,7 +788,7 @@ void MyAvatar::overrideAnimation(const QString& url, float fps, bool loop, float Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame)); return; } - _rig->overrideAnimation(url, fps, loop, firstFrame, lastFrame); + _skeletonModel->getRig().overrideAnimation(url, fps, loop, firstFrame, lastFrame); } void MyAvatar::restoreAnimation() { @@ -803,7 +796,7 @@ void MyAvatar::restoreAnimation() { QMetaObject::invokeMethod(this, "restoreAnimation"); return; } - _rig->restoreAnimation(); + _skeletonModel->getRig().restoreAnimation(); } QStringList MyAvatar::getAnimationRoles() { @@ -812,7 +805,7 @@ QStringList MyAvatar::getAnimationRoles() { QMetaObject::invokeMethod(this, "getAnimationRoles", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QStringList, result)); return result; } - return _rig->getAnimationRoles(); + return _skeletonModel->getRig().getAnimationRoles(); } void MyAvatar::overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, @@ -822,7 +815,7 @@ void MyAvatar::overrideRoleAnimation(const QString& role, const QString& url, fl Q_ARG(float, fps), Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame)); return; } - _rig->overrideRoleAnimation(role, url, fps, loop, firstFrame, lastFrame); + _skeletonModel->getRig().overrideRoleAnimation(role, url, fps, loop, firstFrame, lastFrame); } void MyAvatar::restoreRoleAnimation(const QString& role) { @@ -830,7 +823,7 @@ void MyAvatar::restoreRoleAnimation(const QString& role) { QMetaObject::invokeMethod(this, "restoreRoleAnimation", Q_ARG(const QString&, role)); return; } - _rig->restoreRoleAnimation(role); + _skeletonModel->getRig().restoreRoleAnimation(role); } void MyAvatar::saveData() { @@ -958,6 +951,10 @@ void MyAvatar::setEnableDebugDrawIKConstraints(bool isEnabled) { _enableDebugDrawIKConstraints = isEnabled; } +void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) { + _enableDebugDrawIKChains = isEnabled; +} + void MyAvatar::setEnableMeshVisible(bool isEnabled) { _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene()); } @@ -968,7 +965,7 @@ void MyAvatar::setUseAnimPreAndPostRotations(bool isEnabled) { } void MyAvatar::setEnableInverseKinematics(bool isEnabled) { - _rig->setEnableInverseKinematics(isEnabled); + _skeletonModel->getRig().setEnableInverseKinematics(isEnabled); } void MyAvatar::loadData() { @@ -1217,7 +1214,7 @@ void MyAvatar::setJointData(int index, const glm::quat& rotation, const glm::vec return; } // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); + _skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); } void MyAvatar::setJointRotation(int index, const glm::quat& rotation) { @@ -1226,7 +1223,7 @@ void MyAvatar::setJointRotation(int index, const glm::quat& rotation) { return; } // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointRotation(index, true, rotation, SCRIPT_PRIORITY); + _skeletonModel->getRig().setJointRotation(index, true, rotation, SCRIPT_PRIORITY); } void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) { @@ -1235,7 +1232,7 @@ void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) { return; } // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointTranslation(index, true, translation, SCRIPT_PRIORITY); + _skeletonModel->getRig().setJointTranslation(index, true, translation, SCRIPT_PRIORITY); } void MyAvatar::clearJointData(int index) { @@ -1243,7 +1240,7 @@ void MyAvatar::clearJointData(int index) { QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(int, index)); return; } - _rig->clearJointAnimationPriority(index); + _skeletonModel->getRig().clearJointAnimationPriority(index); } void MyAvatar::clearJointsData() { @@ -1251,7 +1248,7 @@ void MyAvatar::clearJointsData() { QMetaObject::invokeMethod(this, "clearJointsData"); return; } - _rig->clearJointStates(); + _skeletonModel->getRig().clearJointStates(); } void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { @@ -1694,7 +1691,7 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) { _skeletonModel->reset(); // Why is this necessary? Without this, we crash in the next render. _currentAnimGraphUrl.set(url); - _rig->initAnimGraph(url); + _skeletonModel->getRig().initAnimGraph(url); _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes @@ -1710,7 +1707,7 @@ void MyAvatar::initAnimGraph() { graphUrl = QUrl::fromLocalFile(PathUtils::resourcesPath() + "avatar/avatar-animation.json"); } - _rig->initAnimGraph(graphUrl); + _skeletonModel->getRig().initAnimGraph(graphUrl); _currentAnimGraphUrl.set(graphUrl); _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. @@ -1718,7 +1715,7 @@ void MyAvatar::initAnimGraph() { } void MyAvatar::destroyAnimGraph() { - _rig->destroyAnimGraph(); + _skeletonModel->getRig().destroyAnimGraph(); } void MyAvatar::postUpdate(float deltaTime) { @@ -1734,22 +1731,23 @@ void MyAvatar::postUpdate(float deltaTime) { if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { - auto animSkeleton = _rig->getAnimSkeleton(); + auto animSkeleton = _skeletonModel->getRig().getAnimSkeleton(); // the rig is in the skeletonModel frame AnimPose xform(glm::vec3(1), _skeletonModel->getRotation(), _skeletonModel->getTranslation()); if (_enableDebugDrawDefaultPose && animSkeleton) { glm::vec4 gray(0.2f, 0.2f, 0.2f, 0.2f); - AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarDefaultPoses", animSkeleton, _rig->getAbsoluteDefaultPoses(), xform, gray); + AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarDefaultPoses", animSkeleton, _skeletonModel->getRig().getAbsoluteDefaultPoses(), xform, gray); } if (_enableDebugDrawAnimPose && animSkeleton) { // build absolute AnimPoseVec from rig AnimPoseVec absPoses; - absPoses.reserve(_rig->getJointStateCount()); - for (int i = 0; i < _rig->getJointStateCount(); i++) { - absPoses.push_back(AnimPose(_rig->getJointTransform(i))); + const Rig& rig = _skeletonModel->getRig(); + absPoses.reserve(rig.getJointStateCount()); + for (int i = 0; i < rig.getJointStateCount(); i++) { + absPoses.push_back(AnimPose(rig.getJointTransform(i))); } glm::vec4 cyan(0.1f, 0.6f, 0.6f, 1.0f); AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarAnimPoses", animSkeleton, absPoses, xform, cyan); @@ -2338,17 +2336,18 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { const glm::quat hmdOrientation = getHMDSensorOrientation(); const glm::quat hmdOrientationYawOnly = cancelOutRollAndPitch(hmdOrientation); - int rightEyeIndex = _rig->indexOfJoint("RightEye"); - int leftEyeIndex = _rig->indexOfJoint("LeftEye"); - int neckIndex = _rig->indexOfJoint("Neck"); - int hipsIndex = _rig->indexOfJoint("Hips"); + const Rig& rig = _skeletonModel->getRig(); + int rightEyeIndex = rig.indexOfJoint("RightEye"); + int leftEyeIndex = rig.indexOfJoint("LeftEye"); + int neckIndex = rig.indexOfJoint("Neck"); + int hipsIndex = rig.indexOfJoint("Hips"); glm::vec3 rigMiddleEyePos = DEFAULT_AVATAR_MIDDLE_EYE_POS; if (leftEyeIndex >= 0 && rightEyeIndex >= 0) { - rigMiddleEyePos = (_rig->getAbsoluteDefaultPose(leftEyeIndex).trans() + _rig->getAbsoluteDefaultPose(rightEyeIndex).trans()) / 2.0f; + rigMiddleEyePos = (rig.getAbsoluteDefaultPose(leftEyeIndex).trans() + rig.getAbsoluteDefaultPose(rightEyeIndex).trans()) / 2.0f; } - glm::vec3 rigNeckPos = neckIndex != -1 ? _rig->getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS; - glm::vec3 rigHipsPos = hipsIndex != -1 ? _rig->getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS; + glm::vec3 rigNeckPos = neckIndex != -1 ? rig.getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS; + glm::vec3 rigHipsPos = hipsIndex != -1 ? rig.getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS; glm::vec3 localEyes = (rigMiddleEyePos - rigHipsPos); glm::vec3 localNeck = (rigNeckPos - rigHipsPos); @@ -2714,8 +2713,8 @@ glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const { glm::mat4 MyAvatar::getCenterEyeCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int rightEyeIndex = _rig->indexOfJoint("RightEye"); - int leftEyeIndex = _rig->indexOfJoint("LeftEye"); + int rightEyeIndex = _skeletonModel->getRig().indexOfJoint("RightEye"); + int leftEyeIndex = _skeletonModel->getRig().indexOfJoint("LeftEye"); if (rightEyeIndex >= 0 && leftEyeIndex >= 0) { auto centerEyePos = (getAbsoluteDefaultJointTranslationInObjectFrame(rightEyeIndex) + getAbsoluteDefaultJointTranslationInObjectFrame(leftEyeIndex)) * 0.5f; auto centerEyeRot = Quaternions::Y_180; @@ -2727,7 +2726,7 @@ glm::mat4 MyAvatar::getCenterEyeCalibrationMat() const { glm::mat4 MyAvatar::getHeadCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int headIndex = _rig->indexOfJoint("Head"); + int headIndex = _skeletonModel->getRig().indexOfJoint("Head"); if (headIndex >= 0) { auto headPos = getAbsoluteDefaultJointTranslationInObjectFrame(headIndex); auto headRot = getAbsoluteDefaultJointRotationInObjectFrame(headIndex); @@ -2739,7 +2738,7 @@ glm::mat4 MyAvatar::getHeadCalibrationMat() const { glm::mat4 MyAvatar::getSpine2CalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int spine2Index = _rig->indexOfJoint("Spine2"); + int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2"); if (spine2Index >= 0) { auto spine2Pos = getAbsoluteDefaultJointTranslationInObjectFrame(spine2Index); auto spine2Rot = getAbsoluteDefaultJointRotationInObjectFrame(spine2Index); @@ -2751,7 +2750,7 @@ glm::mat4 MyAvatar::getSpine2CalibrationMat() const { glm::mat4 MyAvatar::getHipsCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int hipsIndex = _rig->indexOfJoint("Hips"); + int hipsIndex = _skeletonModel->getRig().indexOfJoint("Hips"); if (hipsIndex >= 0) { auto hipsPos = getAbsoluteDefaultJointTranslationInObjectFrame(hipsIndex); auto hipsRot = getAbsoluteDefaultJointRotationInObjectFrame(hipsIndex); @@ -2763,7 +2762,7 @@ glm::mat4 MyAvatar::getHipsCalibrationMat() const { glm::mat4 MyAvatar::getLeftFootCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int leftFootIndex = _rig->indexOfJoint("LeftFoot"); + int leftFootIndex = _skeletonModel->getRig().indexOfJoint("LeftFoot"); if (leftFootIndex >= 0) { auto leftFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(leftFootIndex); auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex); @@ -2775,7 +2774,7 @@ glm::mat4 MyAvatar::getLeftFootCalibrationMat() const { glm::mat4 MyAvatar::getRightFootCalibrationMat() const { // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed. - int rightFootIndex = _rig->indexOfJoint("RightFoot"); + int rightFootIndex = _skeletonModel->getRig().indexOfJoint("RightFoot"); if (rightFootIndex >= 0) { auto rightFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(rightFootIndex); auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex); @@ -2795,7 +2794,7 @@ bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& o slamPosition(position); setOrientation(orientation); - _rig->setMaxHipsOffsetLength(0.05f); + _skeletonModel->getRig().setMaxHipsOffsetLength(0.05f); auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index); if (it == _pinnedJoints.end()) { @@ -2812,7 +2811,7 @@ bool MyAvatar::clearPinOnJoint(int index) { auto hipsIndex = getJointIndex("Hips"); if (index == hipsIndex) { - _rig->setMaxHipsOffsetLength(FLT_MAX); + _skeletonModel->getRig().setMaxHipsOffsetLength(FLT_MAX); } return true; @@ -2821,7 +2820,7 @@ bool MyAvatar::clearPinOnJoint(int index) { } float MyAvatar::getIKErrorOnLastSolve() const { - return _rig->getIKErrorOnLastSolve(); + return _skeletonModel->getRig().getIKErrorOnLastSolve(); } // thread-safe diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 394d6b2ac7..fde350a43e 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -148,7 +148,7 @@ public: }; Q_ENUM(DriveKeys) - explicit MyAvatar(QThread* thread, RigPointer rig); + explicit MyAvatar(QThread* thread); ~MyAvatar(); void instantiableAvatar() override {}; @@ -322,9 +322,9 @@ public: // adding one of the other handlers. While any handler may change a value in animStateDictionaryIn (or supply different values in animStateDictionaryOut) // a handler must not remove properties from animStateDictionaryIn, nor change property values that it does not intend to change. // It is not specified in what order multiple handlers are called. - Q_INVOKABLE QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { return _rig->addAnimationStateHandler(handler, propertiesList); } + Q_INVOKABLE QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { return _skeletonModel->getRig().addAnimationStateHandler(handler, propertiesList); } // Removes a handler previously added by addAnimationStateHandler. - Q_INVOKABLE void removeAnimationStateHandler(QScriptValue handler) { _rig->removeAnimationStateHandler(handler); } + Q_INVOKABLE void removeAnimationStateHandler(QScriptValue handler) { _skeletonModel->getRig().removeAnimationStateHandler(handler); } Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; } Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } @@ -524,6 +524,7 @@ public slots: void setEnableDebugDrawSensorToWorldMatrix(bool isEnabled); void setEnableDebugDrawIKTargets(bool isEnabled); void setEnableDebugDrawIKConstraints(bool isEnabled); + void setEnableDebugDrawIKChains(bool isEnabled); bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); } void setEnableMeshVisible(bool isEnabled); void setUseAnimPreAndPostRotations(bool isEnabled); @@ -708,7 +709,6 @@ private: glm::quat _goToOrientation; std::unordered_set<int> _headBoneSet; - RigPointer _rig; bool _prevShouldDrawHead; bool _rigEnabled { true }; @@ -718,6 +718,7 @@ private: bool _enableDebugDrawSensorToWorldMatrix { false }; bool _enableDebugDrawIKTargets { false }; bool _enableDebugDrawIKConstraints { false }; + bool _enableDebugDrawIKChains { false }; AudioListenerMode _audioListenerMode; glm::vec3 _customListenPosition; diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp index 793fbb79c4..f02aefec5b 100644 --- a/interface/src/avatar/MyHead.cpp +++ b/interface/src/avatar/MyHead.cpp @@ -44,17 +44,14 @@ glm::quat MyHead::getCameraOrientation() const { void MyHead::simulate(float deltaTime) { auto player = DependencyManager::get<recording::Deck>(); // Only use face trackers when not playing back a recording. - if (player->isPlaying()) { - Parent::simulate(deltaTime); - } else { - computeAudioLoudness(deltaTime); - + if (!player->isPlaying()) { FaceTracker* faceTracker = qApp->getActiveFaceTracker(); - _isFaceTrackerConnected = faceTracker && !faceTracker->isMuted(); + _isFaceTrackerConnected = faceTracker != nullptr && !faceTracker->isMuted(); if (_isFaceTrackerConnected) { _transientBlendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); if (typeid(*faceTracker) == typeid(DdeFaceTracker)) { + if (Menu::getInstance()->isOptionChecked(MenuOption::UseAudioForMouth)) { calculateMouthShapes(deltaTime); @@ -71,19 +68,9 @@ void MyHead::simulate(float deltaTime) { } applyEyelidOffset(getFinalOrientationInWorldFrame()); } - } else { - computeFaceMovement(deltaTime); - } - - auto eyeTracker = DependencyManager::get<EyeTracker>(); - _isEyeTrackerConnected = eyeTracker && eyeTracker->isTracking(); - if (_isEyeTrackerConnected) { - // TODO? figure out where EyeTracker data harvested. Move it here? - _saccade = glm::vec3(); - } else { - computeEyeMovement(deltaTime); } - + auto eyeTracker = DependencyManager::get<EyeTracker>(); + _isEyeTrackerConnected = eyeTracker->isTracking(); } - computeEyePosition(); + Parent::simulate(deltaTime); } diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index e60481fc62..828a5f8a01 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -13,7 +13,7 @@ #include "Application.h" #include "InterfaceLogging.h" -MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer rig) : SkeletonModel(owningAvatar, parent, rig) { +MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent) : SkeletonModel(owningAvatar, parent) { } Rig::CharacterControllerState convertCharacterControllerState(CharacterController::State state) { @@ -63,7 +63,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { glm::mat4 rigToWorld = createMatFromQuatAndPos(getRotation(), getTranslation()); glm::mat4 worldToRig = glm::inverse(rigToWorld); glm::mat4 rigHMDMat = worldToRig * worldHMDMat; - _rig->computeHeadFromHMD(AnimPose(rigHMDMat), headParams.rigHeadPosition, headParams.rigHeadOrientation); + _rig.computeHeadFromHMD(AnimPose(rigHMDMat), headParams.rigHeadPosition, headParams.rigHeadOrientation); headParams.headEnabled = true; } else { // even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and down in desktop mode. @@ -94,7 +94,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { headParams.isTalking = head->getTimeWithoutTalking() <= 1.5f; - _rig->updateFromHeadParameters(headParams, deltaTime); + _rig.updateFromHeadParameters(headParams, deltaTime); Rig::HandAndFeetParameters handAndFeetParams; @@ -138,14 +138,14 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { handAndFeetParams.bodyCapsuleHalfHeight = myAvatar->getCharacterController()->getCapsuleHalfHeight(); handAndFeetParams.bodyCapsuleLocalOffset = myAvatar->getCharacterController()->getCapsuleLocalOffset(); - _rig->updateFromHandAndFeetParameters(handAndFeetParams, deltaTime); + _rig.updateFromHandAndFeetParameters(handAndFeetParams, deltaTime); Rig::CharacterControllerState ccState = convertCharacterControllerState(myAvatar->getCharacterController()->getState()); auto velocity = myAvatar->getLocalVelocity(); auto position = myAvatar->getLocalPosition(); auto orientation = myAvatar->getLocalOrientation(); - _rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState); + _rig.computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState); // evaluate AnimGraph animation and update jointStates. Model::updateRig(deltaTime, parentTransform); @@ -158,6 +158,6 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex; eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex; - _rig->updateFromEyeParameters(eyeParams); + _rig.updateFromEyeParameters(eyeParams); } diff --git a/interface/src/avatar/MySkeletonModel.h b/interface/src/avatar/MySkeletonModel.h index 84fccc825a..12aba6b545 100644 --- a/interface/src/avatar/MySkeletonModel.h +++ b/interface/src/avatar/MySkeletonModel.h @@ -19,7 +19,7 @@ private: using Parent = SkeletonModel; public: - MySkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr, RigPointer rig = nullptr); + MySkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr); void updateRig(float deltaTime, glm::mat4 parentTransform) override; }; diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 3430ffbd15..49517eb38e 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -192,6 +192,7 @@ int main(int argc, const char* argv[]) { int exitCode; { RunningMarker runningMarker(nullptr, RUNNING_MARKER_FILENAME); + bool runningMarkerExisted = runningMarker.fileExists(); runningMarker.writeRunningMarkerFile(); bool noUpdater = parser.isSet(noUpdaterOption); @@ -202,7 +203,7 @@ int main(int argc, const char* argv[]) { SandboxUtils::runLocalSandbox(serverContentPath, true, RUNNING_MARKER_FILENAME, noUpdater); } - Application app(argc, const_cast<char**>(argv), startupTime); + Application app(argc, const_cast<char**>(argv), startupTime, runningMarkerExisted); // Now that the main event loop is setup, launch running marker thread runningMarker.startRunningMarker(); diff --git a/interface/src/ui/SnapshotAnimated.h b/interface/src/ui/SnapshotAnimated.h index 1cf21edfb8..dd32e4893d 100644 --- a/interface/src/ui/SnapshotAnimated.h +++ b/interface/src/ui/SnapshotAnimated.h @@ -51,6 +51,7 @@ private: static void processFrames(); public: static void saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer<WindowScriptingInterface> dm); + static bool isAlreadyTakingSnapshotAnimated() { return snapshotAnimatedFirstFrameTimestamp != 0; }; static Setting::Handle<bool> alsoTakeAnimatedSnapshot; static Setting::Handle<float> snapshotAnimatedDuration; }; diff --git a/interface/src/ui/UpdateDialog.cpp b/interface/src/ui/UpdateDialog.cpp index 60acd0895b..2dcc0c07eb 100644 --- a/interface/src/ui/UpdateDialog.cpp +++ b/interface/src/ui/UpdateDialog.cpp @@ -23,11 +23,8 @@ UpdateDialog::UpdateDialog(QQuickItem* parent) : auto applicationUpdater = DependencyManager::get<AutoUpdater>(); int currentVersion = QCoreApplication::applicationVersion().toInt(); int latestVersion = applicationUpdater.data()->getBuildData().lastKey(); - int versionsBehind = latestVersion - currentVersion; _updateAvailableDetails = "v" + QString::number(latestVersion) + " released on " + QString(applicationUpdater.data()->getBuildData()[latestVersion]["releaseTime"]).replace(" ", " "); - _updateAvailableDetails += "\nYou are " + QString::number(versionsBehind) + " version" - + (versionsBehind > 1 ? "s" : "") + " behind"; _releaseNotes = ""; for (int i = latestVersion; i > currentVersion; i--) { diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 307f23bff3..0bed07891e 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -18,7 +18,7 @@ QString const ModelOverlay::TYPE = "model"; ModelOverlay::ModelOverlay() - : _model(std::make_shared<Model>(std::make_shared<Rig>(), nullptr, this)), + : _model(std::make_shared<Model>(nullptr, this)), _modelTextures(QVariantMap()) { _model->init(); @@ -28,7 +28,7 @@ ModelOverlay::ModelOverlay() ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : Volume3DOverlay(modelOverlay), - _model(std::make_shared<Model>(std::make_shared<Rig>(), nullptr, this)), + _model(std::make_shared<Model>(nullptr, this)), _modelTextures(QVariantMap()), _url(modelOverlay->_url), _updateModel(false), @@ -211,12 +211,10 @@ QVariant ModelOverlay::getProperty(const QString& property) { if (property == "jointNames") { if (_model && _model->isActive()) { // note: going through Rig because Model::getJointNames() (which proxies to FBXGeometry) was always empty - const RigPointer rig = _model->getRig(); - if (rig) { - return mapJoints<QStringList, QString>([rig](int jointIndex) -> QString { - return rig->nameOfJoint(jointIndex); - }); - } + const Rig* rig = &(_model->getRig()); + return mapJoints<QStringList, QString>([rig](int jointIndex) -> QString { + return rig->nameOfJoint(jointIndex); + }); } } diff --git a/libraries/animation/src/AnimContext.cpp b/libraries/animation/src/AnimContext.cpp index 70ca3764b0..c8efd83318 100644 --- a/libraries/animation/src/AnimContext.cpp +++ b/libraries/animation/src/AnimContext.cpp @@ -10,10 +10,11 @@ #include "AnimContext.h" -AnimContext::AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, +AnimContext::AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, bool enableDebugDrawIKChains, const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix) : _enableDebugDrawIKTargets(enableDebugDrawIKTargets), _enableDebugDrawIKConstraints(enableDebugDrawIKConstraints), + _enableDebugDrawIKChains(enableDebugDrawIKChains), _geometryToRigMatrix(geometryToRigMatrix), _rigToWorldMatrix(rigToWorldMatrix) { diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index f68535005c..e8bf5c34eb 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -16,18 +16,20 @@ class AnimContext { public: - AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, + AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, bool enableDebugDrawIKChains, const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix); bool getEnableDebugDrawIKTargets() const { return _enableDebugDrawIKTargets; } bool getEnableDebugDrawIKConstraints() const { return _enableDebugDrawIKConstraints; } + bool getEnableDebugDrawIKChains() const { return _enableDebugDrawIKChains; } const glm::mat4& getGeometryToRigMatrix() const { return _geometryToRigMatrix; } const glm::mat4& getRigToWorldMatrix() const { return _rigToWorldMatrix; } protected: bool _enableDebugDrawIKTargets { false }; - bool _enableDebugDrawIKConstraints{ false }; + bool _enableDebugDrawIKConstraints { false }; + bool _enableDebugDrawIKChains { false }; glm::mat4 _geometryToRigMatrix; glm::mat4 _rigToWorldMatrix; }; diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 4471f11857..d613e42866 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -175,7 +175,7 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: } } -void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets) { +void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets) { // compute absolute poses that correspond to relative target poses AnimPoseVec absolutePoses; absolutePoses.resize(_relativePoses.size()); @@ -193,25 +193,23 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I while (maxError > MAX_ERROR_TOLERANCE && numLoops < MAX_IK_LOOPS) { ++numLoops; + bool debug = context.getEnableDebugDrawIKChains() && numLoops == MAX_IK_LOOPS; + // solve all targets - int lowestMovedIndex = (int)_relativePoses.size(); for (auto& target: targets) { - int lowIndex = solveTargetWithCCD(target, absolutePoses); - if (lowIndex < lowestMovedIndex) { - lowestMovedIndex = lowIndex; - } + solveTargetWithCCD(context, target, absolutePoses, debug); } // harvest accumulated rotations and apply the average - for (int i = lowestMovedIndex; i < _maxTargetIndex; ++i) { + for (int i = 0; i < (int)_relativePoses.size(); ++i) { if (_accumulators[i].size() > 0) { _relativePoses[i].rot() = _accumulators[i].getAverage(); _accumulators[i].clear(); } } - // update the absolutePoses that need it (from lowestMovedIndex to _maxTargetIndex) - for (auto i = lowestMovedIndex; i <= _maxTargetIndex; ++i) { + // update the absolutePoses + for (int i = 0; i < (int)_relativePoses.size(); ++i) { auto parentIndex = _skeleton->getParentIndex((int)i); if (parentIndex != -1) { absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i]; @@ -236,7 +234,9 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I for (auto& target: targets) { int tipIndex = target.getIndex(); int parentIndex = _skeleton->getParentIndex(tipIndex); - if (parentIndex != -1) { + + // update rotationOnly targets that don't lie on the ik chain of other ik targets. + if (parentIndex != -1 && !_accumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) { const glm::quat& targetRotation = target.getRotation(); // compute tip's new parent-relative rotation // Q = Qp * q --> q' = Qp^ * Q @@ -254,24 +254,25 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I } } -int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVec& absolutePoses) { - int lowestMovedIndex = (int)_relativePoses.size(); +void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) { + size_t chainDepth = 0; + IKTarget::Type targetType = target.getType(); if (targetType == IKTarget::Type::RotationOnly) { // the final rotation will be enforced after the iterations // TODO: solve this correctly - return lowestMovedIndex; + return; } int tipIndex = target.getIndex(); int pivotIndex = _skeleton->getParentIndex(tipIndex); if (pivotIndex == -1 || pivotIndex == _hipsIndex) { - return lowestMovedIndex; + return; } int pivotsParentIndex = _skeleton->getParentIndex(pivotIndex); if (pivotsParentIndex == -1) { // TODO?: handle case where tip's parent is root? - return lowestMovedIndex; + return; } // cache tip's absolute orientation @@ -281,31 +282,46 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // the tip's parent-relative as we proceed up the chain glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot(); + std::map<int, DebugJoint> debugJointMap; + // NOTE: if this code is removed, the head will remain rigid, causing the spine/hips to thrust forward backward // as the head is nodded. - if (targetType == IKTarget::Type::HmdHead) { + if (targetType == IKTarget::Type::HmdHead || + targetType == IKTarget::Type::RotationAndPosition || + targetType == IKTarget::Type::HipsRelativeRotationAndPosition) { - // rotate tip directly to target orientation - tipOrientation = target.getRotation(); - glm::quat tipRelativeRotation = glm::inverse(tipParentOrientation) * tipOrientation; + // rotate tip toward target orientation + glm::quat deltaRot = target.getRotation() * glm::inverse(tipOrientation); + + deltaRot *= target.getFlexCoefficient(chainDepth); + glm::normalize(deltaRot); + + // compute parent relative rotation + glm::quat tipRelativeRotation = glm::inverse(tipParentOrientation) * deltaRot * tipOrientation; // then enforce tip's constraint RotationConstraint* constraint = getConstraint(tipIndex); + bool constrained = false; if (constraint) { - bool constrained = constraint->apply(tipRelativeRotation); + constrained = constraint->apply(tipRelativeRotation); if (constrained) { tipOrientation = tipParentOrientation * tipRelativeRotation; tipRelativeRotation = tipRelativeRotation; } } + // store the relative rotation change in the accumulator _accumulators[tipIndex].add(tipRelativeRotation, target.getWeight()); + + if (debug) { + debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, constrained); + } } // cache tip absolute position glm::vec3 tipPosition = absolutePoses[tipIndex].trans(); - size_t chainDepth = 1; + chainDepth++; // descend toward root, pivoting each joint to get tip closer to target position while (pivotIndex != _hipsIndex && pivotsParentIndex != -1) { @@ -388,15 +404,15 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // compute joint's new parent-relative rotation after swing // Q' = dQ * Q and Q = Qp * q --> q' = Qp^ * dQ * Q - glm::quat newRot = glm::normalize(glm::inverse( - absolutePoses[pivotsParentIndex].rot()) * - deltaRotation * - absolutePoses[pivotIndex].rot()); + glm::quat newRot = glm::normalize(glm::inverse(absolutePoses[pivotsParentIndex].rot()) * + deltaRotation * + absolutePoses[pivotIndex].rot()); // enforce pivot's constraint RotationConstraint* constraint = getConstraint(pivotIndex); + bool constrained = false; if (constraint) { - bool constrained = constraint->apply(newRot); + constrained = constraint->apply(newRot); if (constrained) { // the constraint will modify the local rotation of the tip so we must // compute the corresponding model-frame deltaRotation @@ -408,9 +424,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // store the relative rotation change in the accumulator _accumulators[pivotIndex].add(newRot, target.getWeight()); - // this joint has been changed so we check to see if it has the lowest index - if (pivotIndex < lowestMovedIndex) { - lowestMovedIndex = pivotIndex; + if (debug) { + debugJointMap[pivotIndex] = DebugJoint(newRot, constrained); } // keep track of tip's new transform as we descend towards root @@ -423,7 +438,10 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe chainDepth++; } - return lowestMovedIndex; + + if (debug) { + debugDrawIKChain(debugJointMap, context); + } } //virtual @@ -551,7 +569,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars { PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0); - solveWithCyclicCoordinateDescent(targets); + solveWithCyclicCoordinateDescent(context, targets); } if (_hipsTargetIndex < 0) { @@ -562,6 +580,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } } } + return _relativePoses; } @@ -658,8 +677,8 @@ static void setEllipticalSwingLimits(SwingTwistConstraint* stConstraint, float l float dTheta = TWO_PI / NUM_SUBDIVISIONS; float theta = 0.0f; for (int i = 0; i < NUM_SUBDIVISIONS; i++) { - float theta_prime = atanf((lateralSwingPhi / anteriorSwingPhi) * tanf(theta)); - float phi = (cosf(2.0f * theta_prime) * ((lateralSwingPhi - anteriorSwingPhi) / 2.0f)) + ((lateralSwingPhi + anteriorSwingPhi) / 2.0f); + float theta_prime = atanf((anteriorSwingPhi / lateralSwingPhi) * tanf(theta)); + float phi = (cosf(2.0f * theta_prime) * ((anteriorSwingPhi - lateralSwingPhi) / 2.0f)) + ((anteriorSwingPhi + lateralSwingPhi) / 2.0f); minDots.push_back(cosf(phi)); theta += dTheta; } @@ -771,27 +790,36 @@ void AnimInverseKinematics::initConstraints() { std::vector<glm::vec3> swungDirections; float deltaTheta = PI / 4.0f; float theta = 0.0f; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.25f, sinf(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 1.0f, sinf(theta))); // posterior theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta))); // posterior + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), -1.5f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.25f, sinf(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), -3.0f, sinf(theta))); // anterior theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.5f, sinf(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), -1.5f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.5f, sinf(theta))); // anterior + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.5f, sinf(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta))); std::vector<float> minDots; for (size_t i = 0; i < swungDirections.size(); i++) { minDots.push_back(glm::dot(glm::normalize(swungDirections[i]), Vectors::UNIT_Y)); } stConstraint->setSwingLimits(minDots); + + /* + // simple cone + std::vector<float> minDots; + const float MAX_HAND_SWING = 2.9f; // 170 deg //2 * PI / 3.0f; + minDots.push_back(cosf(MAX_HAND_SWING)); + stConstraint->setSwingLimits(minDots); + */ + constraint = static_cast<RotationConstraint*>(stConstraint); } else if (0 == baseName.compare("Hand", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); @@ -839,11 +867,11 @@ void AnimInverseKinematics::initConstraints() { } else if (baseName.startsWith("Shoulder", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot()); - const float MAX_SHOULDER_TWIST = PI / 20.0f; + const float MAX_SHOULDER_TWIST = PI / 10.0f; stConstraint->setTwistLimits(-MAX_SHOULDER_TWIST, MAX_SHOULDER_TWIST); std::vector<float> minDots; - const float MAX_SHOULDER_SWING = PI / 16.0f; + const float MAX_SHOULDER_SWING = PI / 12.0f; minDots.push_back(cosf(MAX_SHOULDER_SWING)); stConstraint->setSwingLimits(minDots); @@ -855,8 +883,8 @@ void AnimInverseKinematics::initConstraints() { stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST); // limit lateral swings more then forward-backward swings - const float MAX_SPINE_LATERAL_SWING = PI / 30.0f; - const float MAX_SPINE_ANTERIOR_SWING = PI / 20.0f; + const float MAX_SPINE_LATERAL_SWING = PI / 15.0f; + const float MAX_SPINE_ANTERIOR_SWING = PI / 10.0f; setEllipticalSwingLimits(stConstraint, MAX_SPINE_LATERAL_SWING, MAX_SPINE_ANTERIOR_SWING); if (0 == baseName.compare("Spine1", Qt::CaseSensitive) @@ -869,12 +897,12 @@ void AnimInverseKinematics::initConstraints() { } else if (0 == baseName.compare("Neck", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot()); - const float MAX_NECK_TWIST = PI / 10.0f; + const float MAX_NECK_TWIST = PI / 8.0f; stConstraint->setTwistLimits(-MAX_NECK_TWIST, MAX_NECK_TWIST); // limit lateral swings more then forward-backward swings - const float MAX_NECK_LATERAL_SWING = PI / 10.0f; - const float MAX_NECK_ANTERIOR_SWING = PI / 8.0f; + const float MAX_NECK_LATERAL_SWING = PI / 12.0f; + const float MAX_NECK_ANTERIOR_SWING = PI / 10.0f; setEllipticalSwingLimits(stConstraint, MAX_NECK_LATERAL_SWING, MAX_NECK_ANTERIOR_SWING); constraint = static_cast<RotationConstraint*>(stConstraint); @@ -884,10 +912,10 @@ void AnimInverseKinematics::initConstraints() { const float MAX_HEAD_TWIST = PI / 6.0f; stConstraint->setTwistLimits(-MAX_HEAD_TWIST, MAX_HEAD_TWIST); - std::vector<float> minDots; - const float MAX_HEAD_SWING = PI / 6.0f; - minDots.push_back(cosf(MAX_HEAD_SWING)); - stConstraint->setSwingLimits(minDots); + // limit lateral swings more then forward-backward swings + const float MAX_NECK_LATERAL_SWING = PI / 4.0f; + const float MAX_NECK_ANTERIOR_SWING = PI / 3.0f; + setEllipticalSwingLimits(stConstraint, MAX_NECK_LATERAL_SWING, MAX_NECK_ANTERIOR_SWING); constraint = static_cast<RotationConstraint*>(stConstraint); } else if (0 == baseName.compare("ForeArm", Qt::CaseSensitive)) { @@ -1044,7 +1072,96 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele static glm::vec3 sphericalToCartesian(float phi, float theta) { float cos_phi = cosf(phi); float sin_phi = sinf(phi); - return glm::vec3(sin_phi * cosf(theta), cos_phi, -sin_phi * sinf(theta)); + return glm::vec3(sin_phi * cosf(theta), cos_phi, sin_phi * sinf(theta)); +} + +void AnimInverseKinematics::debugDrawRelativePoses(const AnimContext& context) const { + AnimPoseVec poses = _relativePoses; + + // convert relative poses to absolute + _skeleton->convertRelativePosesToAbsolute(poses); + + mat4 geomToWorldMatrix = context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(); + + const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); + const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); + const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); + const vec4 GRAY(0.2f, 0.2f, 0.2f, 1.0f); + const float AXIS_LENGTH = 2.0f; // cm + + // draw each pose + for (int i = 0; i < (int)poses.size(); i++) { + + // transform local axes into world space. + auto pose = poses[i]; + glm::vec3 xAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_X); + glm::vec3 yAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Y); + glm::vec3 zAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Z); + glm::vec3 pos = transformPoint(geomToWorldMatrix, pose.trans()); + DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * xAxis, RED); + DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * yAxis, GREEN); + DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * zAxis, BLUE); + + // draw line to parent + int parentIndex = _skeleton->getParentIndex(i); + if (parentIndex != -1) { + glm::vec3 parentPos = transformPoint(geomToWorldMatrix, poses[parentIndex].trans()); + DebugDraw::getInstance().drawRay(pos, parentPos, GRAY); + } + } +} + +void AnimInverseKinematics::debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const { + AnimPoseVec poses = _relativePoses; + + // copy debug joint rotations into the relative poses + for (auto& debugJoint : debugJointMap) { + poses[debugJoint.first].rot() = debugJoint.second.relRot; + } + + // convert relative poses to absolute + _skeleton->convertRelativePosesToAbsolute(poses); + + mat4 geomToWorldMatrix = context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(); + + const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); + const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); + const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); + const vec4 GRAY(0.2f, 0.2f, 0.2f, 1.0f); + const float AXIS_LENGTH = 2.0f; // cm + + // draw each pose + for (int i = 0; i < (int)poses.size(); i++) { + + // only draw joints that are actually in debugJointMap, or their parents + auto iter = debugJointMap.find(i); + auto parentIter = debugJointMap.find(_skeleton->getParentIndex(i)); + if (iter != debugJointMap.end() || parentIter != debugJointMap.end()) { + + // transform local axes into world space. + auto pose = poses[i]; + glm::vec3 xAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_X); + glm::vec3 yAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Y); + glm::vec3 zAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Z); + glm::vec3 pos = transformPoint(geomToWorldMatrix, pose.trans()); + DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * xAxis, RED); + DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * yAxis, GREEN); + DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * zAxis, BLUE); + + // draw line to parent + int parentIndex = _skeleton->getParentIndex(i); + if (parentIndex != -1) { + glm::vec3 parentPos = transformPoint(geomToWorldMatrix, poses[parentIndex].trans()); + glm::vec4 color = GRAY; + + // draw constrained joints with a RED link to their parent. + if (parentIter != debugJointMap.end() && parentIter->second.constrained) { + color = RED; + } + DebugDraw::getInstance().drawRay(pos, parentPos, color); + } + } + } } void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) const { @@ -1056,20 +1173,12 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con const vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f); const vec4 GRAY(0.2f, 0.2f, 0.2f, 1.0f); const vec4 MAGENTA(1.0f, 0.0f, 1.0f, 1.0f); - const float AXIS_LENGTH = 2.0f; // cm + const float AXIS_LENGTH = 5.0f; // cm const float TWIST_LENGTH = 4.0f; // cm - const float HINGE_LENGTH = 6.0f; // cm - const float SWING_LENGTH = 5.0f; // cm + const float HINGE_LENGTH = 4.0f; // cm + const float SWING_LENGTH = 4.0f; // cm - AnimPoseVec poses = _skeleton->getRelativeDefaultPoses(); - - // copy reference rotations into the relative poses - for (int i = 0; i < (int)poses.size(); i++) { - const RotationConstraint* constraint = getConstraint(i); - if (constraint) { - poses[i].rot() = constraint->getReferenceRotation(); - } - } + AnimPoseVec poses = _relativePoses; // convert relative poses to absolute _skeleton->convertRelativePosesToAbsolute(poses); @@ -1127,8 +1236,8 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con glm::vec3 hingeAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * Vectors::UNIT_Y); DebugDraw::getInstance().drawRay(pos, pos + HINGE_LENGTH * hingeAxis, MAGENTA); - glm::quat minRot = glm::angleAxis(swingTwistConstraint->getMinTwist(), Vectors::UNIT_Y); - glm::quat maxRot = glm::angleAxis(swingTwistConstraint->getMaxTwist(), Vectors::UNIT_Y); + glm::quat minRot = glm::angleAxis(swingTwistConstraint->getMinTwist(), refRot * Vectors::UNIT_Y); + glm::quat maxRot = glm::angleAxis(swingTwistConstraint->getMaxTwist(), refRot * Vectors::UNIT_Y); const int NUM_SWING_STEPS = 10; for (int i = 0; i < NUM_SWING_STEPS + 1; i++) { @@ -1140,17 +1249,18 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con // draw swing constraints. const size_t NUM_MIN_DOTS = swingTwistConstraint->getMinDots().size(); const float D_THETA = TWO_PI / (NUM_MIN_DOTS - 1); + const float PI_2 = PI / 2.0f; float theta = 0.0f; for (size_t i = 0, j = NUM_MIN_DOTS - 2; i < NUM_MIN_DOTS - 1; j = i, i++, theta += D_THETA) { // compute swing rotation from theta and phi angles. float phi = acosf(swingTwistConstraint->getMinDots()[i]); - glm::vec3 swungAxis = sphericalToCartesian(phi, theta); + glm::vec3 swungAxis = sphericalToCartesian(phi, theta - PI_2); glm::vec3 worldSwungAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * swungAxis); glm::vec3 swingTip = pos + SWING_LENGTH * worldSwungAxis; float prevPhi = acos(swingTwistConstraint->getMinDots()[j]); float prevTheta = theta - D_THETA; - glm::vec3 prevSwungAxis = sphericalToCartesian(prevPhi, prevTheta); + glm::vec3 prevSwungAxis = sphericalToCartesian(prevPhi, prevTheta - PI_2); glm::vec3 prevWorldSwungAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * prevSwungAxis); glm::vec3 prevSwingTip = pos + SWING_LENGTH * prevWorldSwungAxis; @@ -1159,7 +1269,6 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con } } } - pose.rot() = constraint->computeCenterRotation(); } } } diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 74face6d0b..0267f14650 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -58,13 +58,22 @@ public: protected: void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses); - void solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets); - int solveTargetWithCCD(const IKTarget& target, AnimPoseVec& absolutePoses); + void solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets); + void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug); virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; + struct DebugJoint { + DebugJoint() : relRot(), constrained(false) {} + DebugJoint(const glm::quat& relRotIn, bool constrainedIn) : relRot(relRotIn), constrained(constrainedIn) {} + glm::quat relRot; + bool constrained; + }; + void debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const; + void debugDrawRelativePoses(const AnimContext& context) const; void debugDrawConstraints(const AnimContext& context) const; void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose); void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor); + // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; } diff --git a/libraries/animation/src/IKTarget.cpp b/libraries/animation/src/IKTarget.cpp index c67c0621c3..2fe767b08d 100644 --- a/libraries/animation/src/IKTarget.cpp +++ b/libraries/animation/src/IKTarget.cpp @@ -23,7 +23,6 @@ void IKTarget::setFlexCoefficients(size_t numFlexCoefficientsIn, const float* fl float IKTarget::getFlexCoefficient(size_t chainDepth) const { const float DEFAULT_FLEX_COEFFICIENT = 0.5f; - if (chainDepth < _numFlexCoefficients) { return _flexCoefficients[chainDepth]; } else { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 23db05eb73..99d2deb323 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -954,7 +954,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons updateAnimationStateHandlers(); _animVars.setRigToGeometryTransform(_rigToGeometryTransform); - AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, + AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains, getGeometryToRigTransform(), rigToWorldTransform); // evaluate the animation @@ -1403,7 +1403,6 @@ void Rig::computeAvatarBoundingCapsule( AnimInverseKinematics ikNode("boundingShape"); ikNode.setSkeleton(_animSkeleton); - // AJT: FIX ME!!!!! ensure that empty weights vector does something reasonable.... ikNode.setTargetVars("LeftHand", "leftHandPosition", "leftHandRotation", @@ -1452,7 +1451,7 @@ void Rig::computeAvatarBoundingCapsule( // call overlay twice: once to verify AnimPoseVec joints and again to do the IK AnimNode::Triggers triggersOut; - AnimContext context(false, false, glm::mat4(), glm::mat4()); + AnimContext context(false, false, false, glm::mat4(), glm::mat4()); float dt = 1.0f; // the value of this does not matter ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses()); AnimPoseVec finalPoses = ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses()); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 33b66f91ea..18780d6e64 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -27,7 +27,6 @@ class Rig; class AnimInverseKinematics; -typedef std::shared_ptr<Rig> RigPointer; // Rig instances are reentrant. // However only specific methods thread-safe. Noted below. @@ -232,6 +231,7 @@ public: void setEnableDebugDrawIKTargets(bool enableDebugDrawIKTargets) { _enableDebugDrawIKTargets = enableDebugDrawIKTargets; } void setEnableDebugDrawIKConstraints(bool enableDebugDrawIKConstraints) { _enableDebugDrawIKConstraints = enableDebugDrawIKConstraints; } + void setEnableDebugDrawIKChains(bool enableDebugDrawIKChains) { _enableDebugDrawIKChains = enableDebugDrawIKChains; } // input assumed to be in rig space void computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const; @@ -343,6 +343,7 @@ protected: bool _enableDebugDrawIKTargets { false }; bool _enableDebugDrawIKConstraints { false }; + bool _enableDebugDrawIKChains { false }; private: QMap<int, StateHandler> _stateHandlers; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 9864b3f67a..e03ca83131 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1516,6 +1516,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice // cleanup any previously initialized device if (_audioOutput) { + _audioOutputIODevice.close(); _audioOutput->stop(); //must be deleted in next eventloop cycle when its called from notify() diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 9793ee3288..b79cee238c 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -94,7 +94,6 @@ public: _audio(audio), _unfulfilledReads(0) {} void start() { open(QIODevice::ReadOnly | QIODevice::Unbuffered); } - void stop() { close(); } qint64 readData(char * data, qint64 maxSize) override; qint64 writeData(const char * data, qint64 maxSize) override { return 0; } int getRecentUnfulfilledReads() { int unfulfilledReads = _unfulfilledReads; _unfulfilledReads = 0; return unfulfilledReads; } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 1968a731a4..d78287a0e7 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -98,7 +98,7 @@ void Avatar::setShowNamesAboveHeads(bool show) { showNamesAboveHeads = show; } -Avatar::Avatar(QThread* thread, RigPointer rig) : +Avatar::Avatar(QThread* thread) : _voiceSphereID(GeometryCache::UNKNOWN_ID) { // we may have been created in the network thread, but we live in the main thread @@ -344,9 +344,9 @@ void Avatar::simulate(float deltaTime, bool inView) { if (inView) { Head* head = getHead(); if (_hasNewJointData) { - _skeletonModel->getRig()->copyJointsFromJointData(_jointData); + _skeletonModel->getRig().copyJointsFromJointData(_jointData); glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset()); - _skeletonModel->getRig()->computeExternalPoses(rootTransform); + _skeletonModel->getRig().computeExternalPoses(rootTransform); _jointDataSimulationRate.increment(); _skeletonModel->simulate(deltaTime, true); @@ -684,7 +684,8 @@ void Avatar::simulateAttachments(float deltaTime) { _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRotation)) { model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale()); model->setRotation(jointRotation * attachment.rotation); - model->setScaleToFit(true, getUniformScale() * attachment.scale, true); // hack to force rescale + float scale = getUniformScale() * attachment.scale; + model->setScaleToFit(true, model->getNaturalDimensions() * scale, true); // hack to force rescale model->setSnapModelToCenter(false); // hack to force resnap model->setSnapModelToCenter(true); model->simulate(deltaTime); @@ -907,17 +908,16 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const { glm::quat Avatar::getAbsoluteDefaultJointRotationInObjectFrame(int index) const { glm::quat rotation; - auto rig = _skeletonModel->getRig(); - glm::quat rot = rig->getAnimSkeleton()->getAbsoluteDefaultPose(index).rot(); + glm::quat rot = _skeletonModel->getRig().getAnimSkeleton()->getAbsoluteDefaultPose(index).rot(); return Quaternions::Y_180 * rot; } glm::vec3 Avatar::getAbsoluteDefaultJointTranslationInObjectFrame(int index) const { glm::vec3 translation; - auto rig = _skeletonModel->getRig(); - glm::vec3 trans = rig->getAnimSkeleton()->getAbsoluteDefaultPose(index).trans(); + const Rig& rig = _skeletonModel->getRig(); + glm::vec3 trans = rig.getAnimSkeleton()->getAbsoluteDefaultPose(index).trans(); glm::mat4 y180Mat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3()); - return transformPoint(y180Mat * rig->getGeometryToRigTransform(), trans); + return transformPoint(y180Mat * rig.getGeometryToRigTransform(), trans); } glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { @@ -1083,16 +1083,16 @@ void Avatar::setModelURLFinished(bool success) { // create new model, can return an instance of a SoftAttachmentModel rather then Model -static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, RigPointer rigOverride, bool isCauterized) { +static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, const Rig& rigOverride, bool isCauterized) { if (isSoft) { // cast to std::shared_ptr<Model> - std::shared_ptr<SoftAttachmentModel> softModel = std::make_shared<SoftAttachmentModel>(std::make_shared<Rig>(), nullptr, rigOverride); + std::shared_ptr<SoftAttachmentModel> softModel = std::make_shared<SoftAttachmentModel>(nullptr, rigOverride); if (isCauterized) { softModel->flagAsCauterized(); } return std::dynamic_pointer_cast<Model>(softModel); } else { - return std::make_shared<Model>(std::make_shared<Rig>()); + return std::make_shared<Model>(); } } @@ -1409,21 +1409,19 @@ void Avatar::setParentJointIndex(quint16 parentJointIndex) { QList<QVariant> Avatar::getSkeleton() { SkeletonModelPointer skeletonModel = _skeletonModel; if (skeletonModel) { - RigPointer rig = skeletonModel->getRig(); - if (rig) { - AnimSkeleton::ConstPointer skeleton = rig->getAnimSkeleton(); - if (skeleton) { - QList<QVariant> list; - list.reserve(skeleton->getNumJoints()); - for (int i = 0; i < skeleton->getNumJoints(); i++) { - QVariantMap obj; - obj["name"] = skeleton->getJointName(i); - obj["index"] = i; - obj["parentIndex"] = skeleton->getParentIndex(i); - list.push_back(obj); - } - return list; + const Rig& rig = skeletonModel->getRig(); + AnimSkeleton::ConstPointer skeleton = rig.getAnimSkeleton(); + if (skeleton) { + QList<QVariant> list; + list.reserve(skeleton->getNumJoints()); + for (int i = 0; i < skeleton->getNumJoints(); i++) { + QVariantMap obj; + obj["name"] = skeleton->getJointName(i); + obj["index"] = i; + obj["parentIndex"] = skeleton->getParentIndex(i); + list.push_back(obj); } + return list; } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index ae24caca29..1724d42510 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -75,7 +75,7 @@ public: static void setShowCollisionShapes(bool render); static void setShowNamesAboveHeads(bool show); - explicit Avatar(QThread* thread, RigPointer rig = nullptr); + explicit Avatar(QThread* thread); ~Avatar(); virtual void instantiableAvatar() = 0; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index b4b0929c0c..96ecd86ff4 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -23,8 +23,6 @@ #include "Avatar.h" -const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for - using namespace std; static bool disableEyelidAdjustment { false }; @@ -43,7 +41,9 @@ void Head::reset() { _baseYaw = _basePitch = _baseRoll = 0.0f; } -void Head::computeAudioLoudness(float deltaTime) { +void Head::simulate(float deltaTime) { + const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for + // grab the audio loudness from the owning avatar, if we have one float audioLoudness = _owningAvatar ? _owningAvatar->getAudioLoudness() : 0.0f; @@ -58,99 +58,102 @@ void Head::computeAudioLoudness(float deltaTime) { _longTermAverageLoudness = glm::mix(_longTermAverageLoudness, _averageLoudness, glm::min(deltaTime / AUDIO_LONG_TERM_AVERAGING_SECS, 1.0f)); } - float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz - _audioAttack = audioAttackAveragingRate * _audioAttack + - (1.0f - audioAttackAveragingRate) * fabs((audioLoudness - _longTermAverageLoudness) - _lastLoudness); - _lastLoudness = (audioLoudness - _longTermAverageLoudness); -} + if (!_isFaceTrackerConnected) { + if (!_isEyeTrackerConnected) { + // Update eye saccades + const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f; + const float AVERAGE_SACCADE_INTERVAL = 6.0f; + const float MICROSACCADE_MAGNITUDE = 0.002f; + const float SACCADE_MAGNITUDE = 0.04f; + const float NOMINAL_FRAME_RATE = 60.0f; -void Head::computeEyeMovement(float deltaTime) { - // Update eye saccades - const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f; - const float AVERAGE_SACCADE_INTERVAL = 6.0f; - const float MICROSACCADE_MAGNITUDE = 0.002f; - const float SACCADE_MAGNITUDE = 0.04f; - const float NOMINAL_FRAME_RATE = 60.0f; + if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) { + _saccadeTarget = MICROSACCADE_MAGNITUDE * randVector(); + } else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) { + _saccadeTarget = SACCADE_MAGNITUDE * randVector(); + } + _saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime); + } else { + _saccade = glm::vec3(); + } - if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) { - _saccadeTarget = MICROSACCADE_MAGNITUDE * randVector(); - } else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) { - _saccadeTarget = SACCADE_MAGNITUDE * randVector(); - } - _saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime); + // Detect transition from talking to not; force blink after that and a delay + bool forceBlink = false; + const float TALKING_LOUDNESS = 100.0f; + const float BLINK_AFTER_TALKING = 0.25f; + _timeWithoutTalking += deltaTime; + if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) { + _timeWithoutTalking = 0.0f; + } else if (_timeWithoutTalking - deltaTime < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) { + forceBlink = true; + } - // Detect transition from talking to not; force blink after that and a delay - bool forceBlink = false; - const float TALKING_LOUDNESS = 100.0f; - const float BLINK_AFTER_TALKING = 0.25f; - _timeWithoutTalking += deltaTime; - if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) { - _timeWithoutTalking = 0.0f; - } else if (_timeWithoutTalking - deltaTime < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) { - forceBlink = true; - } + // Update audio attack data for facial animation (eyebrows and mouth) + float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz + _audioAttack = audioAttackAveragingRate * _audioAttack + + (1.0f - audioAttackAveragingRate) * fabs((audioLoudness - _longTermAverageLoudness) - _lastLoudness); + _lastLoudness = (audioLoudness - _longTermAverageLoudness); - const float BLINK_SPEED = 10.0f; - const float BLINK_SPEED_VARIABILITY = 1.0f; - const float BLINK_START_VARIABILITY = 0.25f; - const float FULLY_OPEN = 0.0f; - const float FULLY_CLOSED = 1.0f; - if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) { - // no blinking when brows are raised; blink less with increasing loudness - const float BASE_BLINK_RATE = 15.0f / 60.0f; - const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f; - if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) * - ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { - _leftEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY; - _rightEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY; - if (randFloat() < 0.5f) { - _leftEyeBlink = BLINK_START_VARIABILITY; - } else { - _rightEyeBlink = BLINK_START_VARIABILITY; + const float BROW_LIFT_THRESHOLD = 100.0f; + if (_audioAttack > BROW_LIFT_THRESHOLD) { + _browAudioLift += sqrtf(_audioAttack) * 0.01f; + } + _browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f); + + const float BLINK_SPEED = 10.0f; + const float BLINK_SPEED_VARIABILITY = 1.0f; + const float BLINK_START_VARIABILITY = 0.25f; + const float FULLY_OPEN = 0.0f; + const float FULLY_CLOSED = 1.0f; + if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) { + // no blinking when brows are raised; blink less with increasing loudness + const float BASE_BLINK_RATE = 15.0f / 60.0f; + const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f; + if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) * + ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { + _leftEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY; + _rightEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY; + if (randFloat() < 0.5f) { + _leftEyeBlink = BLINK_START_VARIABILITY; + } else { + _rightEyeBlink = BLINK_START_VARIABILITY; + } + } + } else { + _leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); + _rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); + + if (_leftEyeBlink == FULLY_CLOSED) { + _leftEyeBlinkVelocity = -BLINK_SPEED; + + } else if (_leftEyeBlink == FULLY_OPEN) { + _leftEyeBlinkVelocity = 0.0f; + } + if (_rightEyeBlink == FULLY_CLOSED) { + _rightEyeBlinkVelocity = -BLINK_SPEED; + + } else if (_rightEyeBlink == FULLY_OPEN) { + _rightEyeBlinkVelocity = 0.0f; } } + + // use data to update fake Faceshift blendshape coefficients + calculateMouthShapes(deltaTime); + FaceTracker::updateFakeCoefficients(_leftEyeBlink, + _rightEyeBlink, + _browAudioLift, + _audioJawOpen, + _mouth2, + _mouth3, + _mouth4, + _transientBlendshapeCoefficients); + + applyEyelidOffset(getOrientation()); + } else { - _leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); - _rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); - - if (_leftEyeBlink == FULLY_CLOSED) { - _leftEyeBlinkVelocity = -BLINK_SPEED; - - } else if (_leftEyeBlink == FULLY_OPEN) { - _leftEyeBlinkVelocity = 0.0f; - } - if (_rightEyeBlink == FULLY_CLOSED) { - _rightEyeBlinkVelocity = -BLINK_SPEED; - - } else if (_rightEyeBlink == FULLY_OPEN) { - _rightEyeBlinkVelocity = 0.0f; - } + _saccade = glm::vec3(); } - applyEyelidOffset(getOrientation()); -} - -void Head::computeFaceMovement(float deltaTime) { - // Update audio attack data for facial animation (eyebrows and mouth) - const float BROW_LIFT_THRESHOLD = 100.0f; - if (_audioAttack > BROW_LIFT_THRESHOLD) { - _browAudioLift += sqrtf(_audioAttack) * 0.01f; - } - _browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f); - - // use data to update fake Faceshift blendshape coefficients - calculateMouthShapes(deltaTime); - FaceTracker::updateFakeCoefficients(_leftEyeBlink, - _rightEyeBlink, - _browAudioLift, - _audioJawOpen, - _mouth2, - _mouth3, - _mouth4, - _transientBlendshapeCoefficients); -} - -void Head::computeEyePosition() { _leftEyePosition = _rightEyePosition = getPosition(); if (_owningAvatar) { auto skeletonModel = static_cast<Avatar*>(_owningAvatar)->getSkeletonModel(); @@ -161,13 +164,6 @@ void Head::computeEyePosition() { _eyePosition = 0.5f * (_leftEyePosition + _rightEyePosition); } -void Head::simulate(float deltaTime) { - computeAudioLoudness(deltaTime); - computeFaceMovement(deltaTime); - computeEyeMovement(deltaTime); - computeEyePosition(); -} - void Head::calculateMouthShapes(float deltaTime) { const float JAW_OPEN_SCALE = 0.015f; const float JAW_OPEN_RATE = 0.9f; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.h b/libraries/avatars-renderer/src/avatars-renderer/Head.h index 39331500b5..c5902285b9 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.h @@ -83,11 +83,6 @@ public: float getTimeWithoutTalking() const { return _timeWithoutTalking; } protected: - void computeAudioLoudness(float deltaTime); - void computeEyeMovement(float deltaTime); - void computeFaceMovement(float deltaTime); - void computeEyePosition(); - // disallow copies of the Head, copy of owning Avatar is disallowed too Head(const Head&); Head& operator= (const Head&); diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp index ad69ba24cb..e870e2de12 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp @@ -8,9 +8,9 @@ #include "OtherAvatar.h" -OtherAvatar::OtherAvatar(QThread* thread, RigPointer rig) : Avatar(thread, rig) { +OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { // give the pointer to our head to inherited _headData variable from AvatarData _headData = new Head(this); - _skeletonModel = std::make_shared<SkeletonModel>(this, nullptr, rig); + _skeletonModel = std::make_shared<SkeletonModel>(this, nullptr); connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h index 22a7e1863a..df09d7fd99 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h @@ -13,7 +13,7 @@ class OtherAvatar : public Avatar { public: - explicit OtherAvatar(QThread* thread, RigPointer rig = nullptr); + explicit OtherAvatar(QThread* thread); virtual void instantiableAvatar() override {}; }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index d3453280ac..2a2817e68b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -22,8 +22,8 @@ #include "Avatar.h" #include "Logging.h" -SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer rig) : - CauterizedModel(rig, parent), +SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) : + CauterizedModel(parent), _owningAvatar(owningAvatar), _boundingCapsuleLocalOffset(0.0f), _boundingCapsuleRadius(0.0f), @@ -31,7 +31,6 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer r _defaultEyeModelPosition(glm::vec3(0.0f, 0.0f, 0.0f)), _headClipDistance(DEFAULT_NEAR_CLIP) { - assert(_rig); assert(_owningAvatar); } @@ -41,12 +40,12 @@ SkeletonModel::~SkeletonModel() { void SkeletonModel::initJointStates() { const FBXGeometry& geometry = getFBXGeometry(); glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); - _rig->initJointStates(geometry, modelOffset); + _rig.initJointStates(geometry, modelOffset); // Determine the default eye position for avatar scale = 1.0 int headJointIndex = geometry.headJointIndex; - if (0 > headJointIndex || headJointIndex >= _rig->getJointStateCount()) { - qCWarning(avatars_renderer) << "Bad head joint! Got:" << headJointIndex << "jointCount:" << _rig->getJointStateCount(); + if (0 > headJointIndex || headJointIndex >= _rig.getJointStateCount()) { + qCWarning(avatars_renderer) << "Bad head joint! Got:" << headJointIndex << "jointCount:" << _rig.getJointStateCount(); } glm::vec3 leftEyePosition, rightEyePosition; getEyeModelPositions(leftEyePosition, rightEyePosition); @@ -102,7 +101,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { // If the head is not positioned, updateEyeJoints won't get the math right glm::quat headOrientation; - _rig->getJointRotation(geometry.headJointIndex, headOrientation); + _rig.getJointRotation(geometry.headJointIndex, headOrientation); glm::vec3 eulers = safeEulerAngles(headOrientation); head->setBasePitch(glm::degrees(-eulers.x)); head->setBaseYaw(glm::degrees(eulers.y)); @@ -116,7 +115,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex; eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex; - _rig->updateFromEyeParameters(eyeParams); + _rig.updateFromEyeParameters(eyeParams); } void SkeletonModel::updateAttitude() { @@ -136,7 +135,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { // let rig compute the model offset glm::vec3 registrationPoint; - if (_rig->getModelRegistrationPoint(registrationPoint)) { + if (_rig.getModelRegistrationPoint(registrationPoint)) { setOffset(registrationPoint); } } else { @@ -164,8 +163,8 @@ bool operator<(const IndexValue& firstIndex, const IndexValue& secondIndex) { } bool SkeletonModel::getLeftGrabPosition(glm::vec3& position) const { - int knuckleIndex = _rig->indexOfJoint("LeftHandMiddle1"); - int handIndex = _rig->indexOfJoint("LeftHand"); + int knuckleIndex = _rig.indexOfJoint("LeftHandMiddle1"); + int handIndex = _rig.indexOfJoint("LeftHand"); if (knuckleIndex >= 0 && handIndex >= 0) { glm::quat handRotation; glm::vec3 knucklePosition; @@ -189,8 +188,8 @@ bool SkeletonModel::getLeftGrabPosition(glm::vec3& position) const { } bool SkeletonModel::getRightGrabPosition(glm::vec3& position) const { - int knuckleIndex = _rig->indexOfJoint("RightHandMiddle1"); - int handIndex = _rig->indexOfJoint("RightHand"); + int knuckleIndex = _rig.indexOfJoint("RightHandMiddle1"); + int handIndex = _rig.indexOfJoint("RightHand"); if (knuckleIndex >= 0 && handIndex >= 0) { glm::quat handRotation; glm::vec3 knucklePosition; @@ -304,7 +303,7 @@ float VERY_BIG_MASS = 1.0e6f; // virtual void SkeletonModel::computeBoundingShape() { - if (!isLoaded() || _rig->jointStatesEmpty()) { + if (!isLoaded() || _rig.jointStatesEmpty()) { return; } @@ -316,7 +315,7 @@ void SkeletonModel::computeBoundingShape() { float radius, height; glm::vec3 offset; - _rig->computeAvatarBoundingCapsule(geometry, radius, height, offset); + _rig.computeAvatarBoundingCapsule(geometry, radius, height, offset); float invScale = 1.0f / _owningAvatar->getUniformScale(); _boundingCapsuleRadius = invScale * radius; _boundingCapsuleHeight = invScale * height; diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h index 059dd245fd..db87a37477 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h @@ -28,7 +28,7 @@ class SkeletonModel : public CauterizedModel { public: - SkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr, RigPointer rig = nullptr); + SkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr); ~SkeletonModel(); void initJointStates() override; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 3aa5ab69fa..d82068b8ac 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2030,17 +2030,6 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { version = JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION; } - // The head setOrientation likes to overwrite the avatar orientation, - // so lets do the head first - // Most head data is relative to the avatar, and needs no basis correction, - // but the lookat vector does need correction - if (json.contains(JSON_AVATAR_HEAD)) { - if (!_headData) { - _headData = new HeadData(this); - } - _headData->fromJson(json[JSON_AVATAR_HEAD].toObject()); - } - if (json.contains(JSON_AVATAR_BODY_MODEL)) { auto bodyModelURL = json[JSON_AVATAR_BODY_MODEL].toString(); if (useFrameSkeleton && bodyModelURL != getSkeletonModelURL().toString()) { @@ -2079,6 +2068,14 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { setOrientation(currentBasis->getRotation()); } + // Do after avatar orientation because head look-at needs avatar orientation. + if (json.contains(JSON_AVATAR_HEAD)) { + if (!_headData) { + _headData = new HeadData(this); + } + _headData->fromJson(json[JSON_AVATAR_HEAD].toObject()); + } + if (json.contains(JSON_AVATAR_SCALE)) { setTargetScale((float)json[JSON_AVATAR_SCALE].toDouble()); } diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index 2704b6539c..8ae33a1b4f 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -52,6 +52,13 @@ glm::quat HeadData::getOrientation() const { return _owningAvatar->getOrientation() * getRawOrientation(); } +void HeadData::setHeadOrientation(const glm::quat& orientation) { + glm::quat bodyOrientation = _owningAvatar->getOrientation(); + glm::vec3 eulers = glm::degrees(safeEulerAngles(glm::inverse(bodyOrientation) * orientation)); + _basePitch = eulers.x; + _baseYaw = eulers.y; + _baseRoll = eulers.z; +} void HeadData::setOrientation(const glm::quat& orientation) { // rotate body about vertical axis @@ -61,10 +68,7 @@ void HeadData::setOrientation(const glm::quat& orientation) { _owningAvatar->setOrientation(bodyOrientation); // the rest goes to the head - glm::vec3 eulers = glm::degrees(safeEulerAngles(glm::inverse(bodyOrientation) * orientation)); - _basePitch = eulers.x; - _baseYaw = eulers.y; - _baseRoll = eulers.z; + setHeadOrientation(orientation); } //Lazily construct a lookup map from the blendshapes @@ -173,14 +177,14 @@ void HeadData::fromJson(const QJsonObject& json) { } } - if (json.contains(JSON_AVATAR_HEAD_ROTATION)) { - setOrientation(quatFromJsonValue(json[JSON_AVATAR_HEAD_ROTATION])); - } - if (json.contains(JSON_AVATAR_HEAD_LOOKAT)) { auto relativeLookAt = vec3FromJsonValue(json[JSON_AVATAR_HEAD_LOOKAT]); if (glm::length2(relativeLookAt) > 0.01f) { setLookAtPosition((_owningAvatar->getOrientation() * relativeLookAt) + _owningAvatar->getPosition()); } } + + if (json.contains(JSON_AVATAR_HEAD_ROTATION)) { + setHeadOrientation(quatFromJsonValue(json[JSON_AVATAR_HEAD_ROTATION])); + } } diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index be9d54e93e..0bb38c1dad 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -101,6 +101,8 @@ private: // privatize copy ctor and assignment operator so copies of this object cannot be made HeadData(const HeadData&); HeadData& operator= (const HeadData&); + + void setHeadOrientation(const glm::quat& orientation); }; #endif // hifi_HeadData_h diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 1de476c825..09308baabb 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -582,7 +582,7 @@ ModelPointer EntityTreeRenderer::allocateModel(const QString& url, float loading return model; } - model = std::make_shared<Model>(std::make_shared<Rig>(), nullptr, spatiallyNestableOverride); + model = std::make_shared<Model>(nullptr, spatiallyNestableOverride); model->setLoadingPriority(loadingPriority); model->init(); model->setURL(QUrl(url)); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 8b1ba75259..36273c1f07 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -807,7 +807,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { const FBXMesh& mesh = fbxGeometry.meshes.at(i); if (mesh.clusters.size() > 0) { const FBXCluster& cluster = mesh.clusters.at(0); - auto jointMatrix = _model->getRig()->getJointTransform(cluster.jointIndex); + auto jointMatrix = _model->getRig().getJointTransform(cluster.jointIndex); // we backtranslate by the registration offset so we can apply that offset to the shapeInfo later localTransforms.push_back(invRegistraionOffset * jointMatrix * cluster.inverseBindMatrix); } else { @@ -1080,26 +1080,22 @@ bool RenderableModelEntityItem::setAbsoluteJointRotationInObjectFrame(int index, if (!_model) { return false; } - RigPointer rig = _model->getRig(); - if (!rig) { - return false; - } - - int jointParentIndex = rig->getJointParentIndex(index); + const Rig& rig = _model->getRig(); + int jointParentIndex = rig.getJointParentIndex(index); if (jointParentIndex == -1) { return setLocalJointRotation(index, rotation); } bool success; AnimPose jointParentPose; - success = rig->getAbsoluteJointPoseInRigFrame(jointParentIndex, jointParentPose); + success = rig.getAbsoluteJointPoseInRigFrame(jointParentIndex, jointParentPose); if (!success) { return false; } AnimPose jointParentInversePose = jointParentPose.inverse(); AnimPose jointAbsolutePose; // in rig frame - success = rig->getAbsoluteJointPoseInRigFrame(index, jointAbsolutePose); + success = rig.getAbsoluteJointPoseInRigFrame(index, jointAbsolutePose); if (!success) { return false; } @@ -1113,26 +1109,23 @@ bool RenderableModelEntityItem::setAbsoluteJointTranslationInObjectFrame(int ind if (!_model) { return false; } - RigPointer rig = _model->getRig(); - if (!rig) { - return false; - } + const Rig& rig = _model->getRig(); - int jointParentIndex = rig->getJointParentIndex(index); + int jointParentIndex = rig.getJointParentIndex(index); if (jointParentIndex == -1) { return setLocalJointTranslation(index, translation); } bool success; AnimPose jointParentPose; - success = rig->getAbsoluteJointPoseInRigFrame(jointParentIndex, jointParentPose); + success = rig.getAbsoluteJointPoseInRigFrame(jointParentIndex, jointParentPose); if (!success) { return false; } AnimPose jointParentInversePose = jointParentPose.inverse(); AnimPose jointAbsolutePose; // in rig frame - success = rig->getAbsoluteJointPoseInRigFrame(index, jointAbsolutePose); + success = rig.getAbsoluteJointPoseInRigFrame(index, jointAbsolutePose); if (!success) { return false; } @@ -1248,20 +1241,16 @@ void RenderableModelEntityItem::locationChanged(bool tellPhysics) { } int RenderableModelEntityItem::getJointIndex(const QString& name) const { - if (_model && _model->isActive()) { - RigPointer rig = _model->getRig(); - return rig->indexOfJoint(name); - } - return -1; + return (_model && _model->isActive()) ? _model->getRig().indexOfJoint(name) : -1; } QStringList RenderableModelEntityItem::getJointNames() const { QStringList result; if (_model && _model->isActive()) { - RigPointer rig = _model->getRig(); - int jointCount = rig->getJointStateCount(); + const Rig& rig = _model->getRig(); + int jointCount = rig.getJointStateCount(); for (int jointIndex = 0; jointIndex < jointCount; jointIndex++) { - result << rig->nameOfJoint(jointIndex); + result << rig.nameOfJoint(jointIndex); } } return result; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 66495a7054..6556f18776 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -36,15 +36,17 @@ void RenderableZoneEntityItem::changeProperties(Lambda setNewProperties) { QString oldShapeURL = getCompoundShapeURL(); glm::vec3 oldPosition = getPosition(), oldDimensions = getDimensions(); glm::quat oldRotation = getRotation(); - + setNewProperties(); - + if (oldShapeURL != getCompoundShapeURL()) { if (_model) { - delete _model; + _model.reset(); } - - _model = getModel(); + + _model = std::make_shared<Model>(); + _model->setIsWireframe(true); + _model->init(); _needsInitialSimulation = true; _model->setURL(getCompoundShapeURL()); } @@ -80,35 +82,24 @@ int RenderableZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch return bytesRead; } -Model* RenderableZoneEntityItem::getModel() { - Model* model = new Model(nullptr); - model->setIsWireframe(true); - model->init(); - return model; -} - -void RenderableZoneEntityItem::initialSimulation() { - _model->setScaleToFit(true, getDimensions()); - _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); - _model->setRotation(getRotation()); - _model->setTranslation(getPosition()); - _model->simulate(0.0f); - _needsInitialSimulation = false; -} - void RenderableZoneEntityItem::updateGeometry() { if (_model && !_model->isActive() && hasCompoundShapeURL()) { // Since we have a delayload, we need to update the geometry if it has been downloaded _model->setURL(getCompoundShapeURL()); } if (_model && _model->isActive() && _needsInitialSimulation) { - initialSimulation(); + _model->setScaleToFit(true, getDimensions()); + _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); + _model->setRotation(getRotation()); + _model->setTranslation(getPosition()); + _model->simulate(0.0f); + _needsInitialSimulation = false; } } void RenderableZoneEntityItem::render(RenderArgs* args) { Q_ASSERT(getType() == EntityTypes::Zone); - + if (_drawZoneBoundaries) { switch (getShapeType()) { case SHAPE_TYPE_COMPOUND: { @@ -123,9 +114,9 @@ void RenderableZoneEntityItem::render(RenderArgs* args) { render::Item::Status::Getters statusGetters; makeEntityItemStatusGetters(getThisPointer(), statusGetters); _model->addToScene(scene, transaction); - + scene->enqueueTransaction(transaction); - + _model->setVisibleInScene(getVisible(), scene); } break; @@ -134,7 +125,7 @@ void RenderableZoneEntityItem::render(RenderArgs* args) { case SHAPE_TYPE_SPHERE: { PerformanceTimer perfTimer("zone->renderPrimitive"); glm::vec4 DEFAULT_COLOR(1.0f, 1.0f, 1.0f, 1.0f); - + Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; @@ -159,7 +150,7 @@ void RenderableZoneEntityItem::render(RenderArgs* args) { break; } } - + if ((!_drawZoneBoundaries || getShapeType() != SHAPE_TYPE_COMPOUND) && _model && !_model->needsFixupInScene()) { // If the model is in the scene but doesn't need to be, remove it. @@ -175,11 +166,11 @@ bool RenderableZoneEntityItem::contains(const glm::vec3& point) const { return EntityItem::contains(point); } const_cast<RenderableZoneEntityItem*>(this)->updateGeometry(); - + if (_model && _model->isActive() && EntityItem::contains(point)) { return _model->convexHullContains(point); } - + return false; } @@ -188,7 +179,7 @@ public: RenderableZoneEntityItemMeta(EntityItemPointer entity) : entity(entity){ } typedef render::Payload<RenderableZoneEntityItemMeta> Payload; typedef Payload::DataPointer Pointer; - + EntityItemPointer entity; }; @@ -196,7 +187,7 @@ namespace render { template <> const ItemKey payloadGetKey(const RenderableZoneEntityItemMeta::Pointer& payload) { return ItemKey::Builder::opaqueShape(); } - + template <> const Item::Bound payloadGetBound(const RenderableZoneEntityItemMeta::Pointer& payload) { if (payload && payload->entity) { bool success; @@ -220,7 +211,7 @@ namespace render { bool RenderableZoneEntityItem::addToScene(EntityItemPointer self, const render::ScenePointer& scene, render::Transaction& transaction) { _myMetaItem = scene->allocateID(); - + auto renderData = std::make_shared<RenderableZoneEntityItemMeta>(self); auto renderPayload = std::make_shared<RenderableZoneEntityItemMeta::Payload>(renderData); diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index c81afdab08..7241e34ce8 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -23,7 +23,7 @@ public: RenderableZoneEntityItem(const EntityItemID& entityItemID) : ZoneEntityItem(entityItemID), - _model(NULL), + _model(nullptr), _needsInitialSimulation(true) { } @@ -48,14 +48,12 @@ private: virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); } void notifyBoundChanged(); - Model* getModel(); - void initialSimulation(); void updateGeometry(); template<typename Lambda> void changeProperties(Lambda functor); - Model* _model; + ModelPointer _model; bool _needsInitialSimulation; render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID }; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 14122594fe..1d83365102 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -810,7 +810,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_COLLISIONLESS, bool, updateCollisionless); READ_ENTITY_PROPERTY(PROP_COLLISION_MASK, uint8_t, updateCollisionMask); READ_ENTITY_PROPERTY(PROP_DYNAMIC, bool, updateDynamic); - READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); + READ_ENTITY_PROPERTY(PROP_LOCKED, bool, updateLocked); READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { @@ -1344,6 +1344,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(dynamic, updateDynamic); SET_ENTITY_PROPERTY_FROM_PROPERTIES(created, updateCreated); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, updateLocked); // non-simulation properties below SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript); @@ -1352,7 +1353,6 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionSoundURL, setCollisionSoundURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha); SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked); SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); SET_ENTITY_PROPERTY_FROM_PROPERTIES(marketplaceID, setMarketplaceID); SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); @@ -2769,6 +2769,23 @@ void EntityItem::setLocked(bool value) { }); } +void EntityItem::updateLocked(bool value) { + bool changed { false }; + withWriteLock([&] { + if (_locked != value) { + _locked = value; + changed = true; + } + }); + if (changed) { + markDirtyFlags(Simulation::DIRTY_MOTION_TYPE); + EntityTreePointer tree = getTree(); + if (tree) { + tree->entityChanged(getThisPointer()); + } + } +} + QString EntityItem::getUserData() const { QString result; withReadLock([&] { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 1896893b52..ba6077592e 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -305,6 +305,7 @@ public: bool getLocked() const; void setLocked(bool value); + void updateLocked(bool value); QString getUserData() const; virtual void setUserData(const QString& value); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 6975d017b0..11cddf2634 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -957,6 +957,24 @@ void EntityTree::bumpTimestamp(EntityItemProperties& properties) { //fixme put c properties.setLastEdited(properties.getLastEdited() + LAST_EDITED_SERVERSIDE_BUMP); } +bool EntityTree::isScriptInWhitelist(const QString& scriptProperty) { + + // grab a URL representation of the entity script so we can check the host for this script + auto entityScriptURL = QUrl::fromUserInput(scriptProperty); + + for (const auto& whiteListedPrefix : _entityScriptSourceWhitelist) { + auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix); + + // check if this script URL matches the whitelist domain and, optionally, is beneath the path + if (entityScriptURL.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 && + entityScriptURL.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) { + return true; + } + } + + return false; +} + int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) { @@ -986,7 +1004,8 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c quint64 startFilter = 0, endFilter = 0; quint64 startLogging = 0, endLogging = 0; - bool suppressDisallowedScript = false; + bool suppressDisallowedClientScript = false; + bool suppressDisallowedServerScript = false; bool isPhysics = message.getType() == PacketType::EntityPhysics; _totalEditMessages++; @@ -1011,36 +1030,57 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c } } - if (validEditPacket && !_entityScriptSourceWhitelist.isEmpty() && !properties.getScript().isEmpty()) { - bool passedWhiteList = false; + if (validEditPacket && !_entityScriptSourceWhitelist.isEmpty()) { - // grab a URL representation of the entity script so we can check the host for this script - auto entityScriptURL = QUrl::fromUserInput(properties.getScript()); + bool wasDeletedBecauseOfClientScript = false; - for (const auto& whiteListedPrefix : _entityScriptSourceWhitelist) { - auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix); + // check the client entity script to make sure its URL is in the whitelist + if (!properties.getScript().isEmpty()) { + bool clientScriptPassedWhitelist = isScriptInWhitelist(properties.getScript()); - // check if this script URL matches the whitelist domain and, optionally, is beneath the path - if (entityScriptURL.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 && - entityScriptURL.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) { - passedWhiteList = true; - break; + if (!clientScriptPassedWhitelist) { + if (wantEditLogging()) { + qCDebug(entities) << "User [" << senderNode->getUUID() + << "] attempting to set entity script not on whitelist, edit rejected"; + } + + // If this was an add, we also want to tell the client that sent this edit that the entity was not added. + if (isAdd) { + QWriteLocker locker(&_recentlyDeletedEntitiesLock); + _recentlyDeletedEntityItemIDs.insert(usecTimestampNow(), entityItemID); + validEditPacket = false; + wasDeletedBecauseOfClientScript = true; + } else { + suppressDisallowedClientScript = true; + } } } - if (!passedWhiteList) { - if (wantEditLogging()) { - qCDebug(entities) << "User [" << senderNode->getUUID() << "] attempting to set entity script not on whitelist, edit rejected"; - } - // If this was an add, we also want to tell the client that sent this edit that the entity was not added. - if (isAdd) { - QWriteLocker locker(&_recentlyDeletedEntitiesLock); - _recentlyDeletedEntityItemIDs.insert(usecTimestampNow(), entityItemID); - validEditPacket = passedWhiteList; - } else { - suppressDisallowedScript = true; + // check all server entity scripts to make sure their URLs are in the whitelist + if (!properties.getServerScripts().isEmpty()) { + bool serverScriptPassedWhitelist = isScriptInWhitelist(properties.getServerScripts()); + + if (!serverScriptPassedWhitelist) { + if (wantEditLogging()) { + qCDebug(entities) << "User [" << senderNode->getUUID() + << "] attempting to set server entity script not on whitelist, edit rejected"; + } + + // If this was an add, we also want to tell the client that sent this edit that the entity was not added. + if (isAdd) { + // Make sure we didn't already need to send back a delete because the client script failed + // the whitelist check + if (!wasDeletedBecauseOfClientScript) { + QWriteLocker locker(&_recentlyDeletedEntitiesLock); + _recentlyDeletedEntityItemIDs.insert(usecTimestampNow(), entityItemID); + validEditPacket = false; + } + } else { + suppressDisallowedServerScript = true; + } } } + } if ((isAdd || properties.lifetimeChanged()) && @@ -1075,11 +1115,16 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c if (existingEntity && !isAdd) { - if (suppressDisallowedScript) { + if (suppressDisallowedClientScript) { bumpTimestamp(properties); properties.setScript(existingEntity->getScript()); } + if (suppressDisallowedServerScript) { + bumpTimestamp(properties); + properties.setServerScripts(existingEntity->getServerScripts()); + } + // if the EntityItem exists, then update it startLogging = usecTimestampNow(); if (wantEditLogging()) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index d7e069b005..c938c7e068 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -303,6 +303,8 @@ protected: void notifyNewlyCreatedEntity(const EntityItem& newEntity, const SharedNodePointer& senderNode); + bool isScriptInWhitelist(const QString& scriptURL); + QReadWriteLock _newlyCreatedHooksLock; QVector<NewlyCreatedEntityHook*> _newlyCreatedHooks; diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index 12bfb8e70b..c57926feb4 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -18,11 +18,10 @@ bool GLTexelFormat::isCompressed() const { case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: return true; - break; default: return false; - break; } } @@ -238,6 +237,9 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::COMPRESSED_BC5_XY: result = GL_COMPRESSED_RG_RGTC2; break; + case gpu::COMPRESSED_BC7_SRGBA: + result = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; + break; default: qCWarning(gpugllogging) << "Unknown combination of texel format"; @@ -364,6 +366,9 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::COMPRESSED_BC5_XY: texel.internalFormat = GL_COMPRESSED_RG_RGTC2; break; + case gpu::COMPRESSED_BC7_SRGBA: + texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; + break; default: qCWarning(gpugllogging) << "Unknown combination of texel format"; @@ -634,6 +639,10 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::COMPRESSED_BC5_XY: texel.internalFormat = GL_COMPRESSED_RG_RGTC2; break; + case gpu::COMPRESSED_BC7_SRGBA: + texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; + break; + default: qCWarning(gpugllogging) << "Unknown combination of texel format"; } diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp index 84dc49deba..b61a3b82c4 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -535,12 +535,14 @@ void GLVariableAllocationSupport::processWorkQueue(WorkQueue& workQueue) { vartexture->demote(); workQueue.pop(); addToWorkQueue(texture); + _memoryPressureStateStale = true; break; case MemoryPressureState::Undersubscribed: vartexture->promote(); workQueue.pop(); addToWorkQueue(texture); + _memoryPressureStateStale = true; break; case MemoryPressureState::Transfer: diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 146554952e..968cc0e116 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -112,6 +112,7 @@ void GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: glCompressedTexSubImage2D(_target, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast<GLsizei>(sourceSize), sourcePointer); break; @@ -128,6 +129,7 @@ void GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: glCompressedTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast<GLsizei>(sourceSize), sourcePointer); break; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index 120be923f5..442d456af9 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -142,6 +142,7 @@ void GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: glCompressedTextureSubImage2D(_id, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast<GLsizei>(sourceSize), sourcePointer); break; @@ -156,6 +157,7 @@ void GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: if (glCompressedTextureSubImage2DEXT) { auto target = GLTexture::CUBE_FACE_LAYOUT[face]; glCompressedTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, internalFormat, diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index 43bcd35b88..a4b7411599 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -24,6 +24,7 @@ const Element Element::COLOR_COMPRESSED_SRGB{ VEC4, NUINT8, COMPRESSED_BC1_SRGB const Element Element::COLOR_COMPRESSED_SRGBA_MASK{ VEC4, NUINT8, COMPRESSED_BC1_SRGBA }; const Element Element::COLOR_COMPRESSED_SRGBA{ VEC4, NUINT8, COMPRESSED_BC3_SRGBA }; const Element Element::COLOR_COMPRESSED_XY{ VEC4, NUINT8, COMPRESSED_BC5_XY }; +const Element Element::COLOR_COMPRESSED_SRGBA_HIGH{ VEC4, NUINT8, COMPRESSED_BC7_SRGBA }; const Element Element::VEC2NU8_XY{ VEC2, NUINT8, XY }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 8b7bbdcbed..250d705e18 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -163,6 +163,7 @@ enum Semantic { COMPRESSED_BC3_SRGBA, COMPRESSED_BC4_RED, COMPRESSED_BC5_XY, + COMPRESSED_BC7_SRGBA, _LAST_COMPRESSED, @@ -234,6 +235,7 @@ public: static const Element COLOR_COMPRESSED_SRGBA_MASK; static const Element COLOR_COMPRESSED_SRGBA; static const Element COLOR_COMPRESSED_XY; + static const Element COLOR_COMPRESSED_SRGBA_HIGH; static const Element VEC2NU8_XY; static const Element VEC4F_COLOR_RGBA; static const Element VEC2F_UV; diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 92ead5f616..5f677d7424 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -184,6 +184,11 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) { } std::shared_ptr<storage::FileStorage> KtxStorage::maybeOpenFile() const { + // 1. Try to get the shared ptr + // 2. If it doesn't exist, grab the mutex around its creation + // 3. If it was created before we got the mutex, return it + // 4. Otherwise, create it + std::shared_ptr<storage::FileStorage> file = _cacheFile.lock(); if (file) { return file; @@ -205,7 +210,6 @@ std::shared_ptr<storage::FileStorage> KtxStorage::maybeOpenFile() const { } PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const { - storage::StoragePointer result; auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face); auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face); if (faceSize != 0 && faceOffset != 0) { @@ -221,7 +225,7 @@ PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const { qWarning() << "Failed to get a valid file out of maybeOpenFile " << QString::fromStdString(_filename); } } - return result; + return nullptr; } Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const { @@ -255,8 +259,18 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor } auto file = maybeOpenFile(); + if (!file) { + qWarning() << "Failed to open file to assign mip data " << QString::fromStdString(_filename); + return; + } - auto imageData = file->mutableData(); + auto fileData = file->mutableData(); + if (!fileData) { + qWarning() << "Failed to get mutable data for " << QString::fromStdString(_filename); + return; + } + + auto imageData = fileData; imageData += ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData + _ktxDescriptor->images[level]._imageOffset; imageData += ktx::IMAGE_SIZE_WIDTH; @@ -271,7 +285,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor memcpy(imageData, storage->data(), storage->size()); _minMipLevelAvailable = level; if (_offsetToMinMipKV > 0) { - auto minMipKeyData = file->mutableData() + ktx::KTX_HEADER_SIZE + _offsetToMinMipKV; + auto minMipKeyData = fileData + ktx::KTX_HEADER_SIZE + _offsetToMinMipKV; memcpy(minMipKeyData, (void*)&_minMipLevelAvailable, 1); } } @@ -517,6 +531,8 @@ bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat header.setCompressed(ktx::GLInternalFormat_Compressed::COMPRESSED_RED_RGTC1, ktx::GLBaseInternalFormat::RED); } else if (texelFormat == Format::COLOR_COMPRESSED_XY && mipFormat == Format::COLOR_COMPRESSED_XY) { header.setCompressed(ktx::GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2, ktx::GLBaseInternalFormat::RG); + } else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH && mipFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH) { + header.setCompressed(ktx::GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_BPTC_UNORM, ktx::GLBaseInternalFormat::RGBA); } else { return false; } @@ -575,6 +591,9 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E } else if (header.getGLInternaFormat_Compressed() == ktx::GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2) { mipFormat = Format::COLOR_COMPRESSED_XY; texelFormat = Format::COLOR_COMPRESSED_XY; + } else if (header.getGLInternaFormat_Compressed() == ktx::GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) { + mipFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH; + texelFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH; } else { return false; } diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 32184dfe79..7f0674b17d 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -344,7 +344,7 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) { nvtt::TextureType textureType = nvtt::TextureType_2D; nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; - nvtt::WrapMode wrapMode = nvtt::WrapMode_Repeat; + nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; nvtt::RoundMode roundMode = nvtt::RoundMode_None; nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; @@ -380,6 +380,9 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) { compressionOptions.setFormat(nvtt::Format_BC4); } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_XY) { compressionOptions.setFormat(nvtt::Format_BC5); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH) { + alphaMode = nvtt::AlphaMode_Transparency; + compressionOptions.setFormat(nvtt::Format_BC7); } else if (mipFormat == gpu::Element::COLOR_RGBA_32) { compressionOptions.setFormat(nvtt::Format_RGBA); compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); @@ -934,8 +937,8 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& gpu::Element formatMip; gpu::Element formatGPU; if (isCubeTexturesCompressionEnabled()) { - formatMip = gpu::Element::COLOR_COMPRESSED_SRGBA; - formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA; + formatMip = gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH; + formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH; } else { formatMip = gpu::Element::COLOR_SRGBA_32; formatGPU = gpu::Element::COLOR_SRGBA_32; diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp index b43d015d65..c366daf7ed 100644 --- a/libraries/ktx/src/ktx/KTX.cpp +++ b/libraries/ktx/src/ktx/KTX.cpp @@ -56,6 +56,7 @@ uint32_t Header::evalPixelOrBlockHeight(uint32_t level) const { case GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: // BC3 case GLInternalFormat_Compressed::COMPRESSED_RED_RGTC1: // BC4 case GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2: // BC5 + case GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_BPTC_UNORM: // BC7 return (pixelWidth + 3) / 4; default: throw std::runtime_error("Unknown format"); @@ -81,6 +82,8 @@ size_t Header::evalPixelOrBlockSize() const { return 8; } else if (format == GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2) { return 16; + } else if (format == GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) { + return 16; } } else { auto baseFormat = getGLBaseInternalFormat(); diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp index 2dcd0ea583..23f9d05596 100644 --- a/libraries/ktx/src/ktx/Writer.cpp +++ b/libraries/ktx/src/ktx/Writer.cpp @@ -149,7 +149,8 @@ namespace ktx { for (size_t i = 0; i < descriptors.size(); ++i) { auto ptr = reinterpret_cast<uint32_t*>(currentDestPtr); - *ptr = descriptors[i]._imageSize; + uint32_t imageFaceSize = descriptors[i]._faceSize; + *ptr = imageFaceSize; // the imageSize written in the ktx is the FACE size #ifdef DEBUG ptr++; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 83f1504f00..471448d596 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -28,6 +28,7 @@ #include <SettingHandle.h> +#include "NetworkingConstants.h" #include "NetworkLogging.h" #include "NodeList.h" #include "udt/PacketHeaders.h" @@ -92,6 +93,7 @@ AccountManager::AccountManager(UserAgentGetter userAgentGetter) : } const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash"; +const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner"; void AccountManager::logout() { // a logout means we want to delete the DataServerAccountInfo we currently have for this URL, in-memory and in file @@ -189,6 +191,12 @@ void AccountManager::setAuthURL(const QUrl& authURL) { requestProfile(); } + // prepare to refresh our token if it is about to expire + if (needsToRefreshToken()) { + qCDebug(networking) << "Refreshing access token since it will be expiring soon."; + refreshAccessToken(); + } + // tell listeners that the auth endpoint has changed emit authEndpointChanged(); } @@ -225,6 +233,10 @@ void AccountManager::sendRequest(const QString& path, uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit()); QUrl requestURL = _authURL; + + if (requestURL.isEmpty()) { // Assignment client doesn't set _authURL. + requestURL = NetworkingConstants::METAVERSE_SERVER_URL; + } if (path.startsWith("/")) { requestURL.setPath(path); @@ -443,6 +455,12 @@ bool AccountManager::hasValidAccessToken() { return false; } else { + + if (!_isWaitingForTokenRefresh && needsToRefreshToken()) { + qCDebug(networking) << "Refreshing access token since it will be expiring soon."; + refreshAccessToken(); + } + return true; } } @@ -458,6 +476,15 @@ bool AccountManager::checkAndSignalForAccessToken() { return hasToken; } +bool AccountManager::needsToRefreshToken() { + if (!_accountInfo.getAccessToken().token.isEmpty()) { + qlonglong expireThreshold = QDateTime::currentDateTime().addSecs(1 * 60 * 60).toMSecsSinceEpoch(); + return _accountInfo.getAccessToken().expiryTimestamp < expireThreshold; + } else { + return false; + } +} + void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) { // replace the account info access token with a new OAuthAccessToken OAuthAccessToken newOAuthToken; @@ -490,8 +517,6 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas QUrl grantURL = _authURL; grantURL.setPath("/oauth/token"); - const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner"; - QByteArray postData; postData.append("grant_type=password&"); postData.append("username=" + login + "&"); @@ -515,8 +540,6 @@ void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) { QUrl grantURL = _authURL; grantURL.setPath("/oauth/token"); - const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner"; - QByteArray postData; postData.append("grant_type=password&"); postData.append("steam_auth_ticket=" + QUrl::toPercentEncoding(authSessionTicket) + "&"); @@ -530,6 +553,32 @@ void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) { connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); } +void AccountManager::refreshAccessToken() { + + _isWaitingForTokenRefresh = true; + + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + + QNetworkRequest request; + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); + + QUrl grantURL = _authURL; + grantURL.setPath("/oauth/token"); + + QByteArray postData; + postData.append("grant_type=refresh_token&"); + postData.append("refresh_token=" + QUrl::toPercentEncoding(_accountInfo.getAccessToken().refreshToken) + "&"); + postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE); + + request.setUrl(grantURL); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QNetworkReply* requestReply = networkAccessManager.post(request, postData); + connect(requestReply, &QNetworkReply::finished, this, &AccountManager::refreshAccessTokenFinished); + connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(refreshAccessTokenError(QNetworkReply::NetworkError))); +} + void AccountManager::requestAccessTokenFinished() { QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender()); @@ -568,10 +617,47 @@ void AccountManager::requestAccessTokenFinished() { void AccountManager::requestAccessTokenError(QNetworkReply::NetworkError error) { // TODO: error handling - qCDebug(networking) << "AccountManager requestError - " << error; + qCDebug(networking) << "AccountManager: failed to fetch access token - " << error; emit loginFailed(); } +void AccountManager::refreshAccessTokenFinished() { + QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender()); + + QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); + const QJsonObject& rootObject = jsonResponse.object(); + + if (!rootObject.contains("error")) { + // construct an OAuthAccessToken from the json object + + if (!rootObject.contains("access_token") || !rootObject.contains("expires_in") + || !rootObject.contains("token_type")) { + // TODO: error handling - malformed token response + qCDebug(networking) << "Received a response for refresh grant that is missing one or more expected values."; + } else { + // clear the path from the response URL so we have the right root URL for this access token + QUrl rootURL = requestReply->url(); + rootURL.setPath(""); + + qCDebug(networking) << "Storing an account with a refreshed access-token for" << qPrintable(rootURL.toString()); + + _accountInfo.setAccessTokenFromJSON(rootObject); + + persistAccountToFile(); + } + } else { + qCWarning(networking) << "Error in response for refresh grant - " << rootObject["error_description"].toString(); + } + + _isWaitingForTokenRefresh = false; +} + +void AccountManager::refreshAccessTokenError(QNetworkReply::NetworkError error) { + // TODO: error handling + qCDebug(networking) << "AccountManager: failed to refresh access token - " << error; + _isWaitingForTokenRefresh = false; +} + void AccountManager::requestProfile() { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index eb4d224501..dd2216957f 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -79,6 +79,7 @@ public: bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); } bool hasValidAccessToken(); + bool needsToRefreshToken(); Q_INVOKABLE bool checkAndSignalForAccessToken(); void setAccessTokenForCurrentAuthURL(const QString& accessToken); @@ -97,10 +98,13 @@ public: public slots: void requestAccessToken(const QString& login, const QString& password); void requestAccessTokenWithSteam(QByteArray authSessionTicket); + void refreshAccessToken(); void requestAccessTokenFinished(); + void refreshAccessTokenFinished(); void requestProfileFinished(); void requestAccessTokenError(QNetworkReply::NetworkError error); + void refreshAccessTokenError(QNetworkReply::NetworkError error); void requestProfileError(QNetworkReply::NetworkError error); void logout(); void generateNewUserKeypair() { generateNewKeypair(); } @@ -141,6 +145,7 @@ private: QMap<QNetworkReply*, JSONCallbackParameters> _pendingCallbackMap; DataServerAccountInfo _accountInfo; + bool _isWaitingForTokenRefresh { false }; bool _isAgent { false }; bool _isWaitingForKeypairResponse { false }; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index c66fe8daf0..8fc1d66cf1 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -30,9 +30,9 @@ #include "udt/PacketHeaders.h" #if USE_STABLE_GLOBAL_SERVICES -const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome"; +const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome/hello"; #else -const QString DEFAULT_HIFI_ADDRESS = "hifi://dev-welcome"; +const QString DEFAULT_HIFI_ADDRESS = "hifi://dev-welcome/hello"; #endif const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 0c804fb5b7..452495cf18 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -185,6 +185,10 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { return MOTION_TYPE_STATIC; } + if (_entity->getLocked()) { + return MOTION_TYPE_STATIC; + } + if (_entity->getDynamic()) { if (!_entity->getParentID().isNull()) { // if something would have been dynamic but is a child of something else, force it to be kinematic, instead. diff --git a/libraries/physics/src/ObjectAction.cpp b/libraries/physics/src/ObjectAction.cpp index de14a46be4..87732ded03 100644 --- a/libraries/physics/src/ObjectAction.cpp +++ b/libraries/physics/src/ObjectAction.cpp @@ -58,6 +58,10 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta return; } + if (ownerEntity->getLocked()) { + return; + } + updateActionWorker(deltaTimeStep); } diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp index 2e3d0385cd..f3ee846d39 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp @@ -17,7 +17,7 @@ using namespace render; -CauterizedMeshPartPayload::CauterizedMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform) +CauterizedMeshPartPayload::CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform) : ModelMeshPartPayload(model, meshIndex, partIndex, shapeIndex, transform, offsetTransform) {} void CauterizedMeshPartPayload::updateTransformForCauterizedMesh( @@ -29,8 +29,16 @@ void CauterizedMeshPartPayload::updateTransformForCauterizedMesh( void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const { // Still relying on the raw data from the model - CauterizedModel* skeleton = static_cast<CauterizedModel*>(_model); - bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE) && skeleton->getEnableCauterization(); + bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE); + if (useCauterizedMesh) { + ModelPointer model = _model.lock(); + if (model) { + CauterizedModel* skeleton = static_cast<CauterizedModel*>(model.get()); + useCauterizedMesh = useCauterizedMesh && skeleton->getEnableCauterization(); + } else { + useCauterizedMesh = false; + } + } if (useCauterizedMesh) { if (_cauterizedClusterBuffer) { diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.h b/libraries/render-utils/src/CauterizedMeshPartPayload.h index 010cd6fcb6..5e3135ea84 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.h +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.h @@ -13,7 +13,7 @@ class CauterizedMeshPartPayload : public ModelMeshPartPayload { public: - CauterizedMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform); + CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform); void updateTransformForCauterizedMesh(const Transform& renderTransform, const gpu::BufferPointer& buffer); diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 14625952ea..47ada457a0 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -16,8 +16,8 @@ #include "RenderUtilsLogging.h" -CauterizedModel::CauterizedModel(RigPointer rig, QObject* parent) : - Model(rig, parent) { +CauterizedModel::CauterizedModel(QObject* parent) : + Model(parent) { } CauterizedModel::~CauterizedModel() { @@ -78,7 +78,7 @@ void CauterizedModel::createVisibleRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { - auto ptr = std::make_shared<CauterizedMeshPartPayload>(this, i, partIndex, shapeID, transform, offset); + auto ptr = std::make_shared<CauterizedMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset); _modelMeshRenderItems << std::static_pointer_cast<ModelMeshPartPayload>(ptr); shapeID++; } @@ -107,7 +107,7 @@ void CauterizedModel::updateClusterMatrices() { const FBXMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); - auto jointMatrix = _rig->getJointTransform(cluster.jointIndex); + auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); } @@ -130,14 +130,14 @@ void CauterizedModel::updateClusterMatrices() { glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); - auto cauterizeMatrix = _rig->getJointTransform(geometry.neckJointIndex) * zeroScale; + auto cauterizeMatrix = _rig.getJointTransform(geometry.neckJointIndex) * zeroScale; for (int i = 0; i < _cauterizeMeshStates.size(); i++) { Model::MeshState& state = _cauterizeMeshStates[i]; const FBXMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); - auto jointMatrix = _rig->getJointTransform(cluster.jointIndex); + auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) { jointMatrix = cauterizeMatrix; } @@ -207,11 +207,12 @@ void CauterizedModel::updateRenderItems() { QList<render::ItemID> keys = self->getRenderItems().keys(); foreach (auto itemID, keys) { transaction.updateItem<CauterizedMeshPartPayload>(itemID, [modelTransform, deleteGeometryCounter](CauterizedMeshPartPayload& data) { - if (data._model && data._model->isLoaded()) { + ModelPointer model = data._model.lock(); + if (model && model->isLoaded()) { // Ensure the model geometry was not reset between frames - if (deleteGeometryCounter == data._model->getGeometryCounter()) { + if (deleteGeometryCounter == model->getGeometryCounter()) { // this stuff identical to what happens in regular Model - const Model::MeshState& state = data._model->getMeshState(data._meshIndex); + const Model::MeshState& state = model->getMeshState(data._meshIndex); Transform renderTransform = modelTransform; if (state.clusterMatrices.size() == 1) { renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0])); @@ -219,7 +220,7 @@ void CauterizedModel::updateRenderItems() { data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer); // this stuff for cauterized mesh - CauterizedModel* cModel = static_cast<CauterizedModel*>(data._model); + CauterizedModel* cModel = static_cast<CauterizedModel*>(model.get()); const Model::MeshState& cState = cModel->getCauterizeMeshState(data._meshIndex); renderTransform = modelTransform; if (cState.clusterMatrices.size() == 1) { diff --git a/libraries/render-utils/src/CauterizedModel.h b/libraries/render-utils/src/CauterizedModel.h index dcff7bd12d..d16c928ba6 100644 --- a/libraries/render-utils/src/CauterizedModel.h +++ b/libraries/render-utils/src/CauterizedModel.h @@ -16,7 +16,7 @@ class CauterizedModel : public Model { Q_OBJECT public: - CauterizedModel(RigPointer rig, QObject* parent); + CauterizedModel(QObject* parent); virtual ~CauterizedModel(); void flagAsCauterized() { _isCauterized = true; } diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 2e08420073..b4fd7e7d2a 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -14,7 +14,6 @@ #include <PerfStat.h> #include "DeferredLightingEffect.h" -#include "Model.h" #include "EntityItem.h" using namespace render; @@ -321,13 +320,13 @@ template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, Ren } } -ModelMeshPartPayload::ModelMeshPartPayload(Model* model, int _meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform) : - _model(model), +ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int _meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform) : _meshIndex(_meshIndex), _shapeID(shapeIndex) { - assert(_model && _model->isLoaded()); - auto& modelMesh = _model->getGeometry()->getMeshes().at(_meshIndex); + assert(model && model->isLoaded()); + _model = model; + auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex); updateMeshPart(modelMesh, partIndex); updateTransform(transform, offsetTransform); @@ -335,20 +334,21 @@ ModelMeshPartPayload::ModelMeshPartPayload(Model* model, int _meshIndex, int par } void ModelMeshPartPayload::initCache() { - assert(_model->isLoaded()); + ModelPointer model = _model.lock(); + assert(model && model->isLoaded()); if (_drawMesh) { auto vertexFormat = _drawMesh->getVertexFormat(); _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); _isSkinned = vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT) && vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_INDEX); - const FBXGeometry& geometry = _model->getFBXGeometry(); + const FBXGeometry& geometry = model->getFBXGeometry(); const FBXMesh& mesh = geometry.meshes.at(_meshIndex); _isBlendShaped = !mesh.blendshapes.isEmpty(); } - auto networkMaterial = _model->getGeometry()->getShapeMaterial(_shapeID); + auto networkMaterial = model->getGeometry()->getShapeMaterial(_shapeID); if (networkMaterial) { _drawMaterial = networkMaterial; } @@ -370,29 +370,31 @@ ItemKey ModelMeshPartPayload::getKey() const { ItemKey::Builder builder; builder.withTypeShape(); - if (!_model->isVisible()) { - builder.withInvisible(); - } + ModelPointer model = _model.lock(); + if (model) { + if (!model->isVisible()) { + builder.withInvisible(); + } - if (_model->isLayeredInFront()) { - builder.withLayered(); - } + if (model->isLayeredInFront()) { + builder.withLayered(); + } - if (_isBlendShaped || _isSkinned) { - builder.withDeformed(); - } + if (_isBlendShaped || _isSkinned) { + builder.withDeformed(); + } - if (_drawMaterial) { - auto matKey = _drawMaterial->getKey(); - if (matKey.isTranslucent()) { + if (_drawMaterial) { + auto matKey = _drawMaterial->getKey(); + if (matKey.isTranslucent()) { + builder.withTransparent(); + } + } + + if (_fadeState != FADE_COMPLETE) { builder.withTransparent(); } } - - if (_fadeState != FADE_COMPLETE) { - builder.withTransparent(); - } - return builder.build(); } @@ -400,7 +402,8 @@ int ModelMeshPartPayload::getLayer() const { // MAgic number while we are defining the layering mechanism: const int LAYER_3D_FRONT = 1; const int LAYER_3D = 0; - if (_model->isLayeredInFront()) { + ModelPointer model = _model.lock(); + if (model && model->isLayeredInFront()) { return LAYER_3D_FRONT; } else { return LAYER_3D; @@ -410,15 +413,16 @@ int ModelMeshPartPayload::getLayer() const { ShapeKey ModelMeshPartPayload::getShapeKey() const { // guard against partially loaded meshes - if (!_model || !_model->isLoaded() || !_model->getGeometry()) { + ModelPointer model = _model.lock(); + if (!model || !model->isLoaded() || !model->getGeometry()) { return ShapeKey::Builder::invalid(); } - const FBXGeometry& geometry = _model->getFBXGeometry(); - const auto& networkMeshes = _model->getGeometry()->getMeshes(); + const FBXGeometry& geometry = model->getFBXGeometry(); + const auto& networkMeshes = model->getGeometry()->getMeshes(); // guard against partially loaded meshes - if (_meshIndex >= (int)networkMeshes.size() || _meshIndex >= (int)geometry.meshes.size() || _meshIndex >= (int)_model->_meshStates.size()) { + if (_meshIndex >= (int)networkMeshes.size() || _meshIndex >= (int)geometry.meshes.size() || _meshIndex >= (int)model->_meshStates.size()) { return ShapeKey::Builder::invalid(); } @@ -427,8 +431,8 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const { // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown // to false to rebuild out mesh groups. if (_meshIndex < 0 || _meshIndex >= (int)networkMeshes.size() || _meshIndex > geometry.meshes.size()) { - _model->_needsFixupInScene = true; // trigger remove/add cycle - _model->invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid + model->_needsFixupInScene = true; // trigger remove/add cycle + model->invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid return ShapeKey::Builder::invalid(); } @@ -452,7 +456,7 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const { bool isUnlit = drawMaterialKey.isUnlit(); bool isSkinned = _isSkinned; - bool wireframe = _model->isWireframe(); + bool wireframe = model->isWireframe(); if (wireframe) { isTranslucent = hasTangents = hasSpecular = hasLightmap = isSkinned = false; @@ -488,18 +492,22 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const { void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { if (!_isBlendShaped) { batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); - batch.setInputFormat((_drawMesh->getVertexFormat())); - batch.setInputStream(0, _drawMesh->getVertexStream()); } else { batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); - batch.setInputFormat((_drawMesh->getVertexFormat())); - batch.setInputBuffer(0, _model->_blendedVertexBuffers[_meshIndex], 0, sizeof(glm::vec3)); - batch.setInputBuffer(1, _model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3)); - batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2)); + ModelPointer model = _model.lock(); + if (model) { + batch.setInputBuffer(0, model->_blendedVertexBuffers[_meshIndex], 0, sizeof(glm::vec3)); + batch.setInputBuffer(1, model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3)); + batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2)); + } else { + batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); + batch.setInputFormat((_drawMesh->getVertexFormat())); + batch.setInputStream(0, _drawMesh->getVertexStream()); + } } if (_fadeState != FADE_COMPLETE) { @@ -530,7 +538,10 @@ float ModelMeshPartPayload::computeFadeAlpha() { if (fadeAlpha >= 1.0f) { _fadeState = FADE_COMPLETE; // when fade-in completes we flag model for one last "render item update" - _model->setRenderItemsNeedUpdate(); + ModelPointer model = _model.lock(); + if (model) { + model->setRenderItemsNeedUpdate(); + } return 1.0f; } return Interpolate::simpleNonLinearBlend(fadeAlpha); @@ -539,26 +550,27 @@ float ModelMeshPartPayload::computeFadeAlpha() { void ModelMeshPartPayload::render(RenderArgs* args) { PerformanceTimer perfTimer("ModelMeshPartPayload::render"); - if (!_model->addedToScene() || !_model->isVisible()) { + ModelPointer model = _model.lock(); + if (!model || !model->addedToScene() || !model->isVisible()) { return; // bail asap } if (_fadeState == FADE_WAITING_TO_START) { - if (_model->isLoaded()) { + if (model->isLoaded()) { if (EntityItem::getEntitiesShouldFadeFunction()()) { _fadeStartTime = usecTimestampNow(); _fadeState = FADE_IN_PROGRESS; } else { _fadeState = FADE_COMPLETE; } - _model->setRenderItemsNeedUpdate(); + model->setRenderItemsNeedUpdate(); } else { return; } } - if (_materialNeedsUpdate && _model->getGeometry()->areTexturesLoaded()) { - _model->setRenderItemsNeedUpdate(); + if (_materialNeedsUpdate && model->getGeometry()->areTexturesLoaded()) { + model->setRenderItemsNeedUpdate(); _materialNeedsUpdate = false; } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 11d1bbf6a7..5d12e60ce3 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -21,6 +21,8 @@ #include <model/Geometry.h> +#include "Model.h" + const uint8_t FADE_WAITING_TO_START = 0; const uint8_t FADE_IN_PROGRESS = 1; const uint8_t FADE_COMPLETE = 2; @@ -83,7 +85,7 @@ namespace render { class ModelMeshPartPayload : public MeshPartPayload { public: - ModelMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform); + ModelMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform); typedef render::Payload<ModelMeshPartPayload> Payload; typedef Payload::DataPointer Pointer; @@ -110,7 +112,7 @@ public: void computeAdjustedLocalBound(const QVector<glm::mat4>& clusterMatrices); gpu::BufferPointer _clusterBuffer; - Model* _model; + ModelWeakPointer _model; int _meshIndex; int _shapeID; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index e09afa3f31..e5a25d733e 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -78,7 +78,7 @@ void initCollisionMaterials() { } } -Model::Model(RigPointer rig, QObject* parent, SpatiallyNestable* spatiallyNestableOverride) : +Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride) : QObject(parent), _renderGeometry(), _collisionGeometry(), @@ -96,8 +96,7 @@ Model::Model(RigPointer rig, QObject* parent, SpatiallyNestable* spatiallyNestab _isVisible(true), _blendNumber(0), _appliedBlendNumber(0), - _isWireframe(false), - _rig(rig) + _isWireframe(false) { // we may have been created in the network thread, but we live in the main thread if (_viewState) { @@ -236,13 +235,14 @@ void Model::updateRenderItems() { render::Transaction transaction; foreach (auto itemID, self->_modelMeshRenderItemsMap.keys()) { transaction.updateItem<ModelMeshPartPayload>(itemID, [deleteGeometryCounter](ModelMeshPartPayload& data) { - if (data._model && data._model->isLoaded()) { + ModelPointer model = data._model.lock(); + if (model && model->isLoaded()) { // Ensure the model geometry was not reset between frames - if (deleteGeometryCounter == data._model->_deleteGeometryCounter) { - Transform modelTransform = data._model->getTransform(); + if (deleteGeometryCounter == model->_deleteGeometryCounter) { + Transform modelTransform = model->getTransform(); modelTransform.setScale(glm::vec3(1.0f)); - const Model::MeshState& state = data._model->getMeshState(data._meshIndex); + const Model::MeshState& state = model->getMeshState(data._meshIndex); Transform renderTransform = modelTransform; if (state.clusterMatrices.size() == 1) { renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0])); @@ -271,7 +271,7 @@ void Model::updateRenderItems() { void Model::initJointTransforms() { if (isLoaded()) { glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); - _rig->setModelOffset(modelOffset); + _rig.setModelOffset(modelOffset); } } @@ -281,7 +281,7 @@ void Model::init() { void Model::reset() { if (isLoaded()) { const FBXGeometry& geometry = getFBXGeometry(); - _rig->reset(geometry); + _rig.reset(geometry); } } @@ -294,7 +294,8 @@ bool Model::updateGeometry() { _needsReload = false; - if (_rig->jointStatesEmpty() && getFBXGeometry().joints.size() > 0) { + // TODO: should all Models have a valid _rig? + if (_rig.jointStatesEmpty() && getFBXGeometry().joints.size() > 0) { initJointStates(); assert(_meshStates.empty()); @@ -326,7 +327,7 @@ void Model::initJointStates() { const FBXGeometry& geometry = getFBXGeometry(); glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); - _rig->initJointStates(geometry, modelOffset); + _rig.initJointStates(geometry, modelOffset); } bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, @@ -715,6 +716,11 @@ Extents Model::getBindExtents() const { return scaledExtents; } +glm::vec3 Model::getNaturalDimensions() const { + Extents modelMeshExtents = getUnscaledMeshExtents(); + return modelMeshExtents.maximum - modelMeshExtents.minimum; +} + Extents Model::getMeshExtents() const { if (!isActive()) { return Extents(); @@ -746,19 +752,19 @@ Extents Model::getUnscaledMeshExtents() const { } void Model::clearJointState(int index) { - _rig->clearJointState(index); + _rig.clearJointState(index); } void Model::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) { - _rig->setJointState(index, valid, rotation, translation, priority); + _rig.setJointState(index, valid, rotation, translation, priority); } void Model::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) { - _rig->setJointRotation(index, valid, rotation, priority); + _rig.setJointRotation(index, valid, rotation, priority); } void Model::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) { - _rig->setJointTranslation(index, valid, translation, priority); + _rig.setJointTranslation(index, valid, translation, priority); } int Model::getParentJointIndex(int jointIndex) const { @@ -810,8 +816,10 @@ void Model::setURL(const QUrl& url) { deleteGeometry(); auto resource = DependencyManager::get<ModelCache>()->getGeometryResource(url); - resource->setLoadPriority(this, _loadingPriority); - _renderWatcher.setResource(resource); + if (resource) { + resource->setLoadPriority(this, _loadingPriority); + _renderWatcher.setResource(resource); + } onInvalidate(); } @@ -823,43 +831,43 @@ void Model::loadURLFinished(bool success) { } bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const { - return _rig->getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation); + return _rig.getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation); } bool Model::getJointPosition(int jointIndex, glm::vec3& position) const { - return _rig->getJointPosition(jointIndex, position); + return _rig.getJointPosition(jointIndex, position); } bool Model::getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const { - return _rig->getJointRotationInWorldFrame(jointIndex, rotation, _rotation); + return _rig.getJointRotationInWorldFrame(jointIndex, rotation, _rotation); } bool Model::getJointRotation(int jointIndex, glm::quat& rotation) const { - return _rig->getJointRotation(jointIndex, rotation); + return _rig.getJointRotation(jointIndex, rotation); } bool Model::getJointTranslation(int jointIndex, glm::vec3& translation) const { - return _rig->getJointTranslation(jointIndex, translation); + return _rig.getJointTranslation(jointIndex, translation); } bool Model::getAbsoluteJointRotationInRigFrame(int jointIndex, glm::quat& rotationOut) const { - return _rig->getAbsoluteJointRotationInRigFrame(jointIndex, rotationOut); + return _rig.getAbsoluteJointRotationInRigFrame(jointIndex, rotationOut); } bool Model::getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& translationOut) const { - return _rig->getAbsoluteJointTranslationInRigFrame(jointIndex, translationOut); + return _rig.getAbsoluteJointTranslationInRigFrame(jointIndex, translationOut); } bool Model::getRelativeDefaultJointRotation(int jointIndex, glm::quat& rotationOut) const { - return _rig->getRelativeDefaultJointRotation(jointIndex, rotationOut); + return _rig.getRelativeDefaultJointRotation(jointIndex, rotationOut); } bool Model::getRelativeDefaultJointTranslation(int jointIndex, glm::vec3& translationOut) const { - return _rig->getRelativeDefaultJointTranslation(jointIndex, translationOut); + return _rig.getRelativeDefaultJointTranslation(jointIndex, translationOut); } bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const { - return _rig->getJointCombinedRotation(jointIndex, rotation, _rotation); + return _rig.getJointCombinedRotation(jointIndex, rotation, _rotation); } QStringList Model::getJointNames() const { @@ -936,8 +944,8 @@ void Blender::run() { Q_ARG(const QVector<glm::vec3>&, normals)); } -void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) { - if (_scaleToFit != scaleToFit || _scaleToFitDimensions != dimensions) { +void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) { + if (forceRescale || _scaleToFit != scaleToFit || _scaleToFitDimensions != dimensions) { _scaleToFit = scaleToFit; _scaleToFitDimensions = dimensions; _scaledToFit = false; // force rescaling @@ -1047,7 +1055,7 @@ void Model::simulate(float deltaTime, bool fullUpdate) { void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { _needsUpdateClusterMatrices = true; glm::mat4 rigToWorldTransform = createMatFromQuatAndPos(getRotation(), getTranslation()); - _rig->updateAnimations(deltaTime, parentTransform, rigToWorldTransform); + _rig.updateAnimations(deltaTime, parentTransform, rigToWorldTransform); } void Model::computeMeshPartLocalBounds() { @@ -1071,7 +1079,7 @@ void Model::updateClusterMatrices() { const FBXMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); - auto jointMatrix = _rig->getJointTransform(cluster.jointIndex); + auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); } @@ -1098,19 +1106,19 @@ void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm: const FBXGeometry& geometry = getFBXGeometry(); const QVector<int>& freeLineage = geometry.joints.at(endIndex).freeLineage; glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset); - _rig->inverseKinematics(endIndex, targetPosition, targetRotation, priority, freeLineage, parentTransform); + _rig.inverseKinematics(endIndex, targetPosition, targetRotation, priority, freeLineage, parentTransform); } bool Model::restoreJointPosition(int jointIndex, float fraction, float priority) { const FBXGeometry& geometry = getFBXGeometry(); const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage; - return _rig->restoreJointPosition(jointIndex, fraction, priority, freeLineage); + return _rig.restoreJointPosition(jointIndex, fraction, priority, freeLineage); } float Model::getLimbLength(int jointIndex) const { const FBXGeometry& geometry = getFBXGeometry(); const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage; - return _rig->getLimbLength(jointIndex, freeLineage, _scale, geometry.joints); + return _rig.getLimbLength(jointIndex, freeLineage, _scale, geometry.joints); } bool Model::maybeStartBlender() { @@ -1153,7 +1161,7 @@ void Model::deleteGeometry() { _deleteGeometryCounter++; _blendedVertexBuffers.clear(); _meshStates.clear(); - _rig->destroyAnimGraph(); + _rig.destroyAnimGraph(); _blendedBlendshapeCoefficients.clear(); _renderGeometry.reset(); _collisionGeometry.reset(); @@ -1223,7 +1231,7 @@ void Model::createVisibleRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { - _modelMeshRenderItems << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset); + _modelMeshRenderItems << std::make_shared<ModelMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset); shapeID++; } } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 5899ccf6b5..d718145d66 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -68,7 +68,7 @@ public: static void setAbstractViewStateInterface(AbstractViewStateInterface* viewState) { _viewState = viewState; } - Model(RigPointer rig, QObject* parent = nullptr, SpatiallyNestable* spatiallyNestableOverride = nullptr); + Model(QObject* parent = nullptr, SpatiallyNestable* spatiallyNestableOverride = nullptr); virtual ~Model(); inline ModelPointer getThisPointer() const { @@ -122,8 +122,6 @@ public: void init(); void reset(); - void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions); - void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint); bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } @@ -164,6 +162,7 @@ public: const glm::vec3& getOffset() const { return _offset; } void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false); + void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale = false); bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled void setSnapModelToCenter(bool snapModelToCenter) { @@ -174,7 +173,7 @@ public: } /// Returns the number of joint states in the model. - int getJointStateCount() const { return (int)_rig->getJointStateCount(); } + int getJointStateCount() const { return (int)_rig.getJointStateCount(); } bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const; bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const; bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const; @@ -209,6 +208,8 @@ public: const glm::vec3& getTranslation() const { return _translation; } const glm::quat& getRotation() const { return _rotation; } + glm::vec3 getNaturalDimensions() const; + Transform getTransform() const; void setScale(const glm::vec3& scale); @@ -223,7 +224,8 @@ public: return ((index < 0) && (index >= _blendshapeCoefficients.size())) ? 0.0f : _blendshapeCoefficients.at(index); } - virtual RigPointer getRig() const { return _rig; } + Rig& getRig() { return _rig; } + const Rig& getRig() const { return _rig; } const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } @@ -390,7 +392,7 @@ protected: mutable bool _needsUpdateTextures { true }; friend class ModelMeshPartPayload; - RigPointer _rig; + Rig _rig; uint32_t _deleteGeometryCounter { 0 }; diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index 8fef0f8f77..f15b696006 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -8,11 +8,9 @@ #include "SoftAttachmentModel.h" -SoftAttachmentModel::SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride) : - CauterizedModel(rig, parent), +SoftAttachmentModel::SoftAttachmentModel(QObject* parent, const Rig& rigOverride) : + CauterizedModel(parent), _rigOverride(rigOverride) { - assert(_rig); - assert(_rigOverride); } SoftAttachmentModel::~SoftAttachmentModel() { @@ -24,11 +22,11 @@ void SoftAttachmentModel::updateRig(float deltaTime, glm::mat4 parentTransform) } int SoftAttachmentModel::getJointIndexOverride(int i) const { - QString name = _rig->nameOfJoint(i); + QString name = _rig.nameOfJoint(i); if (name.isEmpty()) { return -1; } - return _rigOverride->indexOfJoint(name); + return _rigOverride.indexOfJoint(name); } // virtual @@ -51,10 +49,10 @@ void SoftAttachmentModel::updateClusterMatrices() { // TODO: cache these look-ups as an optimization int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); glm::mat4 jointMatrix; - if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride->getJointStateCount()) { - jointMatrix = _rigOverride->getJointTransform(jointIndexOverride); + if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { + jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); } else { - jointMatrix = _rig->getJointTransform(cluster.jointIndex); + jointMatrix = _rig.getJointTransform(cluster.jointIndex); } glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); } diff --git a/libraries/render-utils/src/SoftAttachmentModel.h b/libraries/render-utils/src/SoftAttachmentModel.h index b66c1a289a..4335c1634e 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.h +++ b/libraries/render-utils/src/SoftAttachmentModel.h @@ -23,7 +23,7 @@ class SoftAttachmentModel : public CauterizedModel { Q_OBJECT public: - SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride); + SoftAttachmentModel(QObject* parent, const Rig& rigOverride); ~SoftAttachmentModel(); void updateRig(float deltaTime, glm::mat4 parentTransform) override; @@ -32,7 +32,7 @@ public: protected: int getJointIndexOverride(int i) const; - RigPointer _rigOverride; + const Rig& _rigOverride; }; #endif // hifi_SoftAttachmentModel_h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 8bbb9a3e2c..03d1eb5868 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -231,7 +231,7 @@ void ScriptEngine::disconnectNonEssentialSignals() { // Ensure the thread should be running, and does exist if (_isRunning && _isThreaded && (workerThread = thread())) { connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit); - connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); + connect(this, &QObject::destroyed, workerThread, &QObject::deleteLater); } } @@ -346,7 +346,7 @@ void ScriptEngine::runInThread() { // disconnectNonEssentialSignals() method connect(workerThread, &QThread::started, this, &ScriptEngine::run); connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit); - connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); + connect(this, &QObject::destroyed, workerThread, &QObject::deleteLater); workerThread->start(); } @@ -397,16 +397,12 @@ void ScriptEngine::waitTillDoneRunning() { } } - // NOTE: This will be called on the main application thread from stopAllScripts. - // The application thread will need to continue to process events, because + // NOTE: This will be called on the main application thread (among other threads) from stopAllScripts. + // The thread will need to continue to process events, because // the scripts will likely need to marshall messages across to the main thread, e.g. // if they access Settings or Menu in any of their shutdown code. So: - // Process events for the main application thread, allowing invokeMethod calls to pass between threads. + // Process events for this thread, allowing invokeMethod calls to pass between threads. QCoreApplication::processEvents(); - // In some cases (debugging), processEvents may give the thread enough time to shut down, so recheck it. - if (!thread()) { - break; - } // Avoid a pure busy wait QThread::yieldCurrentThread(); diff --git a/libraries/shared/src/RunningMarker.cpp b/libraries/shared/src/RunningMarker.cpp index f8aaee42df..0c1fd06df8 100644 --- a/libraries/shared/src/RunningMarker.cpp +++ b/libraries/shared/src/RunningMarker.cpp @@ -53,6 +53,11 @@ RunningMarker::~RunningMarker() { _runningMarkerThread->deleteLater(); } +bool RunningMarker::fileExists() const { + QFile runningMarkerFile(getFilePath()); + return runningMarkerFile.exists(); +} + void RunningMarker::writeRunningMarkerFile() { QFile runningMarkerFile(getFilePath()); @@ -69,7 +74,7 @@ void RunningMarker::deleteRunningMarkerFile() { } } -QString RunningMarker::getFilePath() { +QString RunningMarker::getFilePath() const { return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name; } diff --git a/libraries/shared/src/RunningMarker.h b/libraries/shared/src/RunningMarker.h index 1137dbf5fa..f9c8e72d37 100644 --- a/libraries/shared/src/RunningMarker.h +++ b/libraries/shared/src/RunningMarker.h @@ -25,9 +25,11 @@ public: void startRunningMarker(); - QString getFilePath(); + QString getFilePath() const; static QString getMarkerFilePath(QString name); + bool fileExists() const; + void writeRunningMarkerFile(); void deleteRunningMarkerFile(); diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp index e479559e6a..b07f896df0 100644 --- a/libraries/shared/src/shared/Storage.cpp +++ b/libraries/shared/src/shared/Storage.cpp @@ -70,7 +70,15 @@ StoragePointer FileStorage::create(const QString& filename, size_t size, const u } FileStorage::FileStorage(const QString& filename) : _file(filename) { - if (_file.open(QFile::ReadOnly)) { + bool opened = _file.open(QFile::ReadWrite); + if (opened) { + _hasWriteAccess = true; + } else { + _hasWriteAccess = false; + opened = _file.open(QFile::ReadOnly); + } + + if (opened) { _mapped = _file.map(0, _file.size()); if (_mapped) { _valid = true; @@ -91,35 +99,4 @@ FileStorage::~FileStorage() { if (_file.isOpen()) { _file.close(); } -} - -void FileStorage::ensureWriteAccess() { - if (_hasWriteAccess) { - return; - } - - if (_mapped) { - if (!_file.unmap(_mapped)) { - throw std::runtime_error("Unable to unmap file"); - } - } - if (_file.isOpen()) { - _file.close(); - } - _valid = false; - _mapped = nullptr; - - if (_file.open(QFile::ReadWrite)) { - _mapped = _file.map(0, _file.size()); - if (_mapped) { - _valid = true; - _hasWriteAccess = true; - } else { - qCWarning(storagelogging) << "Failed to map file " << _file.fileName(); - throw std::runtime_error("Failed to map file"); - } - } else { - qCWarning(storagelogging) << "Failed to open file " << _file.fileName(); - throw std::runtime_error("Failed to open file"); - } } \ No newline at end of file diff --git a/libraries/shared/src/shared/Storage.h b/libraries/shared/src/shared/Storage.h index 4cad9fa083..d7946738cf 100644 --- a/libraries/shared/src/shared/Storage.h +++ b/libraries/shared/src/shared/Storage.h @@ -60,11 +60,10 @@ namespace storage { FileStorage& operator=(const FileStorage& other) = delete; const uint8_t* data() const override { return _mapped; } - uint8_t* mutableData() override { ensureWriteAccess(); return _mapped; } + uint8_t* mutableData() override { return _hasWriteAccess ? _mapped : nullptr; } size_t size() const override { return _file.size(); } operator bool() const override { return _valid; } private: - void ensureWriteAccess(); bool _valid { false }; bool _hasWriteAccess { false }; diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index f962c7b624..6c5829d64f 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -44,6 +44,7 @@ function showSetupComplete() { '<p>Snapshot location set.</p>' + '<p>Press the big red button to take a snap!</p>' + '</div>'; + document.getElementById("snap-button").disabled = false; } function showSnapshotInstructions() { var snapshotImagesDiv = document.getElementById("snapshot-images"); @@ -69,7 +70,6 @@ function login() { })); } function clearImages() { - document.getElementById("snap-button").disabled = false; var snapshotImagesDiv = document.getElementById("snapshot-images"); snapshotImagesDiv.classList.remove("snapshotInstructions"); while (snapshotImagesDiv.hasChildNodes()) { @@ -300,7 +300,7 @@ function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPrevi if (!isGifLoading) { appendShareBar(id, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast); } - if (!isGifLoading && !isShowingPreviousImages) { + if (!isGifLoading || (isShowingPreviousImages && !image_data.story_id)) { shareForUrl(id); } if (isShowingPreviousImages && isLoggedIn && image_data.story_id) { @@ -650,6 +650,7 @@ window.onload = function () { shareForUrl("p1"); appendShareBar("p1", messageOptions.isLoggedIn, messageOptions.canShare, true, false, false, messageOptions.canBlast); document.getElementById("p1").classList.remove("processingGif"); + document.getElementById("snap-button").disabled = false; } } else { imageCount = message.image_data.length; @@ -688,6 +689,9 @@ function takeSnapshot() { type: "snapshot", action: "takeSnapshot" })); + if (document.getElementById('stillAndGif').checked === true) { + document.getElementById("snap-button").disabled = true; + } } function testInBrowser(test) { diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 8b5ae3c9a7..4c661482fc 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -138,10 +138,10 @@ function onMessage(message) { isLoggedIn = Account.isLoggedIn(); if (isLoggedIn) { print('Sharing snapshot with audience "for_url":', message.data); - Window.shareSnapshot(message.data, message.href || href); + Window.shareSnapshot(message.data, Settings.getValue("previousSnapshotHref")); } else { shareAfterLogin = true; - snapshotToShareAfterLogin.push({ path: message.data, href: message.href || href }); + snapshotToShareAfterLogin.push({ path: message.data, href: Settings.getValue("previousSnapshotHref") }); } } }); @@ -349,6 +349,7 @@ function takeSnapshot() { // We will record snapshots based on the starting location. That could change, e.g., when recording a .gif. // Even the domainId could change (e.g., if the user falls into a teleporter while recording). href = location.href; + Settings.setValue("previousSnapshotHref", href); domainId = location.domainId; Settings.setValue("previousSnapshotDomainID", domainId); diff --git a/server-console/src/main.js b/server-console/src/main.js index 4ce1ccfb02..98bda9a10f 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -42,7 +42,7 @@ const appIcon = path.join(__dirname, '../resources/console.png'); const DELETE_LOG_FILES_OLDER_THAN_X_SECONDS = 60 * 60 * 24 * 7; // 7 Days const LOG_FILE_REGEX = /(domain-server|ac-monitor|ac)-.*-std(out|err).txt/; -const HOME_CONTENT_URL = "http://cdn.highfidelity.com/content-sets/home-tutorial-RC39.tar.gz"; +const HOME_CONTENT_URL = "http://cdn.highfidelity.com/content-sets/home-tutorial-RC40.tar.gz"; function getBuildInfo() { var buildInfoPath = null; diff --git a/tests/animation/src/RigTests.cpp b/tests/animation/src/RigTests.cpp index e8d4c41dff..d5de9226c0 100644 --- a/tests/animation/src/RigTests.cpp +++ b/tests/animation/src/RigTests.cpp @@ -47,7 +47,7 @@ #include <Rig.h> #include "RigTests.h" -static void reportJoint(RigPointer rig, int index) { // Handy for debugging +static void reportJoint(const Rig& rig, int index) { // Handy for debugging std::cout << "\n"; std::cout << index << " " << rig->getAnimSkeleton()->getJointName(index).toUtf8().data() << "\n"; glm::vec3 pos; @@ -58,16 +58,16 @@ static void reportJoint(RigPointer rig, int index) { // Handy for debugging std::cout << " rot:" << safeEulerAngles(rot) << "\n"; std::cout << "\n"; } -static void reportByName(RigPointer rig, const QString& name) { +static void reportByName(const Rig& rig, const QString& name) { int jointIndex = rig->indexOfJoint(name); reportJoint(rig, jointIndex); } -static void reportAll(RigPointer rig) { +static void reportAll(const Rig& rig) { for (int i = 0; i < rig->getJointStateCount(); i++) { reportJoint(rig, i); } } -static void reportSome(RigPointer rig) { +static void reportSome(const Rig& rig) { QString names[] = {"Head", "Neck", "RightShoulder", "RightArm", "RightForeArm", "RightHand", "Spine2", "Spine1", "Spine", "Hips", "RightUpLeg", "RightLeg", "RightFoot", "RightToeBase", "RightToe_End"}; for (auto name : names) { reportByName(rig, name); @@ -91,8 +91,7 @@ void RigTests::initTestCase() { #endif QVERIFY((bool)geometry); - _rig = std::make_shared<Rig>(); - _rig->initJointStates(*geometry, glm::mat4()); + _rig.initJointStates(*geometry, glm::mat4()); std::cout << "Rig is ready " << geometry->joints.count() << " joints " << std::endl; reportAll(_rig); } diff --git a/tests/animation/src/RigTests.h b/tests/animation/src/RigTests.h index 4280c0a8fa..3242c27b99 100644 --- a/tests/animation/src/RigTests.h +++ b/tests/animation/src/RigTests.h @@ -43,13 +43,13 @@ class RigTests : public QObject { Q_OBJECT - + private slots: void initTestCase(); void initialPoseArmsDown(); private: - RigPointer _rig; + Rig _rig; }; #endif // hifi_RigTests_h