diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 2fc905c6fd..d1c2c6597b 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -439,7 +439,7 @@ void Agent::executeScript() { encodedBuffer = audio; } - AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, + AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, false, audioTransform, scriptedAvatar->getWorldPosition(), glm::vec3(0), packetType, _selectedCodecName); }); diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 49453c6fc6..2560f43337 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -275,17 +275,28 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) { if (micStreamIt == _audioStreams.end()) { // we don't have a mic stream yet, so add it - // read the channel flag to see if our stream is stereo or not + // hop past the sequence number that leads the packet message.seek(sizeof(quint16)); - quint8 channelFlag; - message.readPrimitive(&channelFlag); + // pull the codec string from the packet + auto codecString = message.readString(); - bool isStereo = channelFlag == 1; + // determine if the stream is stereo or not + bool isStereo; + if (packetType == PacketType::SilentAudioFrame + || packetType == PacketType::ReplicatedSilentAudioFrame) { + quint16 numSilentSamples; + message.readPrimitive(&numSilentSamples); + isStereo = numSilentSamples == AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; + } else { + quint8 channelFlag; + message.readPrimitive(&channelFlag); + isStereo = channelFlag == 1; + } auto avatarAudioStream = new AvatarAudioStream(isStereo, AudioMixer::getStaticJitterFrames()); - avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO); - qCDebug(audio) << "creating new AvatarAudioStream... codec:" << _selectedCodecName; + avatarAudioStream->setupCodec(_codec, _selectedCodecName, isStereo ? AudioConstants::STEREO : AudioConstants::MONO); + qCDebug(audio) << "creating new AvatarAudioStream... codec:" << _selectedCodecName << "isStereo:" << isStereo; connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioMixerClientData::handleMismatchAudioFormat); @@ -324,7 +335,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) { #if INJECTORS_SUPPORT_CODECS injectorStream->setupCodec(_codec, _selectedCodecName, isStereo ? AudioConstants::STEREO : AudioConstants::MONO); - qCDebug(audio) << "creating new injectorStream... codec:" << _selectedCodecName; + qCDebug(audio) << "creating new injectorStream... codec:" << _selectedCodecName << "isStereo:" << isStereo; #endif auto emplaced = _audioStreams.emplace( @@ -567,7 +578,8 @@ void AudioMixerClientData::setupCodec(CodecPluginPointer codec, const QString& c auto avatarAudioStream = getAvatarAudioStream(); if (avatarAudioStream) { - avatarAudioStream->setupCodec(codec, codecName, AudioConstants::MONO); + avatarAudioStream->setupCodec(codec, codecName, avatarAudioStream->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO); + qCDebug(audio) << "setting AvatarAudioStream... codec:" << _selectedCodecName << "isStereo:" << avatarAudioStream->isStereo(); } #if INJECTORS_SUPPORT_CODECS diff --git a/assignment-client/src/audio/AvatarAudioStream.cpp b/assignment-client/src/audio/AvatarAudioStream.cpp index 1e0c3ed9e6..42495b4dd0 100644 --- a/assignment-client/src/audio/AvatarAudioStream.cpp +++ b/assignment-client/src/audio/AvatarAudioStream.cpp @@ -11,6 +11,7 @@ #include +#include "AudioLogging.h" #include "AvatarAudioStream.h" AvatarAudioStream::AvatarAudioStream(bool isStereo, int numStaticJitterFrames) : @@ -41,6 +42,15 @@ int AvatarAudioStream::parseStreamProperties(PacketType type, const QByteArray& _ringBuffer.resizeForFrameSize(isStereo ? AudioConstants::NETWORK_FRAME_SAMPLES_STEREO : AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); + // restart the codec + if (_codec) { + if (_decoder) { + _codec->releaseDecoder(_decoder); + } + _decoder = _codec->createDecoder(AudioConstants::SAMPLE_RATE, isStereo ? AudioConstants::STEREO : AudioConstants::MONO); + } + qCDebug(audio) << "resetting AvatarAudioStream... codec:" << _selectedCodecName << "isStereo:" << isStereo; + _isStereo = isStereo; } diff --git a/interface/resources/qml/controls/TabletWebScreen.qml b/interface/resources/qml/controls/TabletWebScreen.qml index 501e321f0d..bb037ad478 100644 --- a/interface/resources/qml/controls/TabletWebScreen.qml +++ b/interface/resources/qml/controls/TabletWebScreen.qml @@ -41,9 +41,9 @@ Item { onNewViewRequestedCallback: { // desktop is not defined for web-entities or tablet if (typeof desktop !== "undefined") { - desktop.openBrowserWindow(request, profile); + desktop.openBrowserWindow(request, webViewCoreProfile); } else { - tabletRoot.openBrowserWindow(request, profile); + tabletRoot.openBrowserWindow(request, webViewCoreProfile); } } diff --git a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml deleted file mode 100644 index 5949adffca..0000000000 --- a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml +++ /dev/null @@ -1,85 +0,0 @@ -// -// AvatarBrowser.qml -// -// Created by Bradley Austin Davis on 30 Aug 2015 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtWebChannel 1.0 -import QtWebEngine 1.2 - -import "../../windows" -import "../../controls-uit" -import "../../styles-uit" - -Window { - id: root - HifiConstants { id: hifi } - width: 900; height: 700 - resizable: true - modality: Qt.ApplicationModal - - Item { - anchors.fill: parent - - property bool keyboardEnabled: false - property bool keyboardRaised: true - property bool punctuationMode: false - - BaseWebView { - id: webview - url: Account.metaverseServerURL + "/marketplace?category=avatars" - focus: true - - anchors { - top: parent.top - left: parent.left - right: parent.right - bottom: keyboard.top - } - - // Create a global EventBridge object for raiseAndLowerKeyboard. - WebEngineScript { - id: createGlobalEventBridge - sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.DocumentCreation - worldId: WebEngineScript.MainWorld - } - - // Detect when may want to raise and lower keyboard. - WebEngineScript { - id: raiseAndLowerKeyboard - injectionPoint: WebEngineScript.Deferred - sourceUrl: resourceDirectoryUrl + "html/raiseAndLowerKeyboard.js" - worldId: WebEngineScript.MainWorld - } - - userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] - - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - } - } - - Keyboard { - id: keyboard - raised: parent.keyboardEnabled && parent.keyboardRaised - numeric: parent.punctuationMode - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } - } - - Component.onCompleted: { - keyboardEnabled = HMD.active; - } - } -} diff --git a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml index b27827d9d7..0efc3776b3 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml @@ -99,25 +99,9 @@ Preference { leftMargin: dataTextField.acceptableInput ? hifi.dimensions.contentSpacing.x : 0 } onClicked: { - if (typeof desktop !== "undefined") { - // Load dialog via OffscreenUi so that JavaScript EventBridge is available. - root.browser = OffscreenUi.load("dialogs/preferences/AvatarBrowser.qml"); - root.browser.windowDestroyed.connect(function(){ - root.browser = null; - }); - } else { - root.browser = tabletAvatarBrowserBuilder.createObject(tabletRoot); - - // Make dialog modal. - tabletRoot.openModal = root.browser; - } + ApplicationInterface.loadAvatarBrowser(); } } - Component { - id: tabletAvatarBrowserBuilder; - TabletAvatarBrowser { } - } - } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index 409833df98..f0f123f6c0 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -25,7 +25,6 @@ Item { id: root; property string keyFilePath; - property bool showDebugButtons: true; Connections { target: Commerce; @@ -55,37 +54,6 @@ Item { // Style color: hifi.colors.blueHighlight; } - HifiControlsUit.Button { - id: clearCachedPassphraseButton; - visible: root.showDebugButtons; - color: hifi.buttons.black; - colorScheme: hifi.colorSchemes.dark; - anchors.top: parent.top; - anchors.left: helpTitleText.right; - anchors.leftMargin: 20; - height: 40; - width: 150; - text: "DBG: Clear Pass"; - onClicked: { - Commerce.setPassphrase(""); - sendSignalToWallet({method: 'passphraseReset'}); - } - } - HifiControlsUit.Button { - id: resetButton; - visible: root.showDebugButtons; - color: hifi.buttons.red; - colorScheme: hifi.colorSchemes.dark; - anchors.top: clearCachedPassphraseButton.top; - anchors.left: clearCachedPassphraseButton.right; - height: 40; - width: 150; - text: "DBG: RST Wallet"; - onClicked: { - Commerce.reset(); - sendSignalToWallet({method: 'walletReset'}); - } - } ListModel { id: helpModel; diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletAvatarBrowser.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletAvatarBrowser.qml deleted file mode 100644 index 2ea12f1d3d..0000000000 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletAvatarBrowser.qml +++ /dev/null @@ -1,111 +0,0 @@ -// -// TabletAvatarBrowser.qml -// -// Created by David Rowe on 14 Mar 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 -import QtQuick.Controls 1.4 -import QtWebChannel 1.0 -import QtWebEngine 1.2 - -import "../../../../windows" -import "../../../../controls-uit" -import "../../../../styles-uit" - -Item { - id: root - objectName: "ModelBrowserDialog" - - property string title: "Attachment Model" - - property bool keyboardEnabled: false - property bool keyboardRaised: false - property bool punctuationMode: false - - anchors.fill: parent - - BaseWebView { - id: webview - url: (Account.metaverseServerURL + "/marketplace?category=avatars") - focus: true - - anchors { - top: parent.top - left: parent.left - right: parent.right - bottom: footer.top - } - - // Create a global EventBridge object for raiseAndLowerKeyboard. - WebEngineScript { - id: createGlobalEventBridge - sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.DocumentCreation - worldId: WebEngineScript.MainWorld - } - - // Detect when may want to raise and lower keyboard. - WebEngineScript { - id: raiseAndLowerKeyboard - injectionPoint: WebEngineScript.Deferred - sourceUrl: resourceDirectoryUrl + "html/raiseAndLowerKeyboard.js" - worldId: WebEngineScript.MainWorld - } - - userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] - - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - } - } - - Rectangle { - id: footer - height: 40 - - anchors { - left: parent.left - right: parent.right - bottom: keyboard.top - } - - color: hifi.colors.baseGray - - Row { - anchors { - verticalCenter: parent.verticalCenter - right: parent.right - rightMargin: hifi.dimensions.contentMargin.x - } - - Button { - text: "Cancel" - color: hifi.buttons.white - onClicked: root.destroy(); - } - } - } - - Keyboard { - id: keyboard - - raised: parent.keyboardEnabled && parent.keyboardRaised - numeric: parent.punctuationMode - - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } - } - - Component.onCompleted: { - keyboardEnabled = HMD.active; - } -} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 027e5bb6e9..8e228e1e09 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1132,7 +1132,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo updateHeartbeat(); // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success. - DependencyManager::get()->initializeShapePipelines(); + // DependencyManager::get()->initializeShapePipelines(); // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. @@ -2256,6 +2256,9 @@ void Application::initializeGL() { _renderEngine->load(); _renderEngine->registerScene(_main3DScene); + // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success. + DependencyManager::get()->initializeShapePipelines(); + _offscreenContext = new OffscreenGLCanvas(); _offscreenContext->setObjectName("MainThreadContext"); _offscreenContext->create(_glWidget->qglContext()); @@ -6869,6 +6872,15 @@ void Application::loadAddAvatarBookmarkDialog() const { avatarBookmarks->addBookmark(); } +void Application::loadAvatarBrowser() const { + auto tablet = dynamic_cast(DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); + // construct the url to the marketplace item + QString url = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace?category=avatars"; + QString MARKETPLACES_INJECT_SCRIPT_PATH = "file:///" + qApp->applicationDirPath() + "/scripts/system/html/js/marketplacesInject.js"; + tablet->gotoWebScreen(url, MARKETPLACES_INJECT_SCRIPT_PATH); + DependencyManager::get()->openTablet(); +} + void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) { postLambdaEvent([notify, includeAnimated, aspectRatio, this] { // Get a screenshot and save it diff --git a/interface/src/Application.h b/interface/src/Application.h index d1d3392667..6be198ede3 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -312,6 +312,7 @@ public slots: void toggleEntityScriptServerLogDialog(); Q_INVOKABLE void showAssetServerWidget(QString filePath = ""); Q_INVOKABLE void loadAddAvatarBookmarkDialog() const; + Q_INVOKABLE void loadAvatarBrowser() const; Q_INVOKABLE SharedSoundPointer getSampleSound() const; void showDialog(const QUrl& widgetUrl, const QUrl& tabletUrl, const QString& name) const; diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 51658ddef8..15830636f0 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -189,13 +189,6 @@ void Ledger::history(const QStringList& keys, const int& pageNumber) { keysQuery("history", "historySuccess", "historyFailure", params); } -// The api/failResponse is called just for the side effect of logging. -void Ledger::resetSuccess(QNetworkReply& reply) { apiResponse("reset", reply); } -void Ledger::resetFailure(QNetworkReply& reply) { failResponse("reset", reply); } -void Ledger::reset() { - send("reset_user_hfc_account", "resetSuccess", "resetFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, QJsonObject()); -} - void Ledger::accountSuccess(QNetworkReply& reply) { // lets set the appropriate stuff in the wallet now auto wallet = DependencyManager::get(); diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 5d90aa0808..318006b645 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -31,7 +31,6 @@ public: void inventory(const QStringList& keys); void history(const QStringList& keys, const int& pageNumber); void account(); - void reset(); void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false); void certificateInfo(const QString& certificateId); @@ -66,8 +65,6 @@ public slots: void inventoryFailure(QNetworkReply& reply); void historySuccess(QNetworkReply& reply); void historyFailure(QNetworkReply& reply); - void resetSuccess(QNetworkReply& reply); - void resetFailure(QNetworkReply& reply); void accountSuccess(QNetworkReply& reply); void accountFailure(QNetworkReply& reply); void updateLocationSuccess(QNetworkReply& reply); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 320c7e041c..58548eb3ef 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -128,18 +128,6 @@ void QmlCommerce::generateKeyPair() { getWalletAuthenticatedStatus(); } -void QmlCommerce::reset() { - auto ledger = DependencyManager::get(); - auto wallet = DependencyManager::get(); - ledger->reset(); - wallet->reset(); -} - -void QmlCommerce::resetLocalWalletOnly() { - auto wallet = DependencyManager::get(); - wallet->reset(); -} - void QmlCommerce::account() { auto ledger = DependencyManager::get(); ledger->account(); diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index f2e6c82021..c04ede86c4 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -62,8 +62,6 @@ protected: Q_INVOKABLE void inventory(); Q_INVOKABLE void history(const int& pageNumber); Q_INVOKABLE void generateKeyPair(); - Q_INVOKABLE void reset(); - Q_INVOKABLE void resetLocalWalletOnly(); Q_INVOKABLE void account(); Q_INVOKABLE void certificateInfo(const QString& certificateId); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index c3c91e82a8..8b73042ada 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -144,15 +144,13 @@ bool writeKeys(const char* filename, EC_KEY* keys) { if ((fp = fopen(filename, "wt"))) { if (!PEM_write_EC_PUBKEY(fp, keys)) { fclose(fp); - qCDebug(commerce) << "failed to write public key"; - QFile(QString(filename)).remove(); + qCCritical(commerce) << "failed to write public key"; return retval; } if (!PEM_write_ECPrivateKey(fp, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) { fclose(fp); - qCDebug(commerce) << "failed to write private key"; - QFile(QString(filename)).remove(); + qCCritical(commerce) << "failed to write private key"; return retval; } @@ -168,7 +166,8 @@ bool writeKeys(const char* filename, EC_KEY* keys) { QPair generateECKeypair() { EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1); - QPair retval; + QPair retval{}; + EC_KEY_set_asn1_flag(keyPair, OPENSSL_EC_NAMED_CURVE); if (!EC_KEY_generate_key(keyPair)) { qCDebug(commerce) << "Error generating EC Keypair -" << ERR_get_error(); @@ -517,6 +516,9 @@ bool Wallet::generateKeyPair() { qCInfo(commerce) << "Generating keypair."; auto keyPair = generateECKeypair(); + if (!keyPair.first) { + return false; + } writeBackupInstructions(); @@ -653,20 +655,6 @@ QString Wallet::getKeyFilePath() { } } -void Wallet::reset() { - _publicKeys.clear(); - - delete _securityImage; - _securityImage = nullptr; - - // tell the provider we got nothing - updateImageProvider(); - _passphrase->clear(); - - - QFile keyFile(keyFilePath()); - keyFile.remove(); -} bool Wallet::writeWallet(const QString& newPassphrase) { EC_KEY* keys = readKeys(keyFilePath().toStdString().c_str()); if (keys) { diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index ed145df451..fe3a9f1d5f 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -49,8 +49,6 @@ public: bool walletIsAuthenticatedWithPassphrase(); bool changePassphrase(const QString& newPassphrase); - void reset(); - void getWalletStatus(); enum WalletStatus { WALLET_STATUS_NOT_LOGGED_IN = 0, diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index d17fbebf3a..c8388c3b12 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -591,19 +591,31 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const glm::vec3 d = basePose.trans() - topPose.trans(); float dLen = glm::length(d); if (dLen > EPSILON) { + glm::vec3 dUnit = d / dLen; glm::vec3 e = midPose.xformVector(target.getPoleReferenceVector()); + + // if mid joint is straight use the reference vector to compute eProj, otherwise use reference vector. + // however if mid joint angle is in between the two blend between both solutions. + vec3 u = normalize(basePose.trans() - midPose.trans()); + vec3 v = normalize(topPose.trans() - midPose.trans()); + + const float LERP_THRESHOLD = 3.05433f; // 175 deg + const float BENT_THRESHOLD = 2.96706f; // 170 deg + + float jointAngle = acos(dot(u, v)); + if (jointAngle < BENT_THRESHOLD) { + glm::vec3 midPoint = topPose.trans() + d * 0.5f; + e = normalize(midPose.trans() - midPoint); + } else if (jointAngle < LERP_THRESHOLD) { + glm::vec3 midPoint = topPose.trans() + d * 0.5f; + float alpha = (jointAngle - LERP_THRESHOLD) / (BENT_THRESHOLD - LERP_THRESHOLD); + e = lerp(e, normalize(midPose.trans() - midPoint), alpha); + } + glm::vec3 eProj = e - glm::dot(e, dUnit) * dUnit; float eProjLen = glm::length(eProj); - const float MIN_EPROJ_LEN = 0.5f; - if (eProjLen < MIN_EPROJ_LEN) { - glm::vec3 midPoint = topPose.trans() + d * 0.5f; - e = midPose.trans() - midPoint; - eProj = e - glm::dot(e, dUnit) * dUnit; - eProjLen = glm::length(eProj); - } - glm::vec3 p = target.getPoleVector(); glm::vec3 pProj = p - glm::dot(p, dUnit) * dUnit; float pProjLen = glm::length(pProj); @@ -634,16 +646,27 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix()); - glm::vec3 dUnit = d / dLen; glm::vec3 e = midPose.xformVector(target.getPoleReferenceVector()); - glm::vec3 eProj = e - glm::dot(e, dUnit) * dUnit; - float eProjLen = glm::length(eProj); - const float MIN_EPROJ_LEN = 0.5f; - if (eProjLen < MIN_EPROJ_LEN) { + + // if mid joint is straight use the reference vector to compute eProj, otherwise use reference vector. + // however if mid joint angle is in between the two blend between both solutions. + vec3 u = normalize(basePose.trans() - midPose.trans()); + vec3 v = normalize(topPose.trans() - midPose.trans()); + + const float LERP_THRESHOLD = 3.05433f; // 175 deg + const float BENT_THRESHOLD = 2.96706f; // 170 deg + + float jointAngle = acos(dot(u, v)); + glm::vec4 eColor = RED; + if (jointAngle < BENT_THRESHOLD) { glm::vec3 midPoint = topPose.trans() + d * 0.5f; - e = midPose.trans() - midPoint; - eProj = e - glm::dot(e, dUnit) * dUnit; - eProjLen = glm::length(eProj); + e = normalize(midPose.trans() - midPoint); + eColor = GREEN; + } else if (jointAngle < LERP_THRESHOLD) { + glm::vec3 midPoint = topPose.trans() + d * 0.5f; + float alpha = (jointAngle - LERP_THRESHOLD) / (BENT_THRESHOLD - LERP_THRESHOLD); + e = lerp(e, normalize(midPose.trans() - midPoint), alpha); + eColor = YELLOW; } glm::vec3 p = target.getPoleVector(); @@ -655,7 +678,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const YELLOW); DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint), geomToWorldPose.xformPoint(midPoint + PROJ_VECTOR_LEN * glm::normalize(e)), - RED); + eColor); DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(midPoint), geomToWorldPose.xformPoint(midPoint + POLE_VECTOR_LEN * glm::normalize(p)), BLUE); diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp index 470bbab8b6..a0b8fba1da 100644 --- a/libraries/animation/src/AnimPose.cpp +++ b/libraries/animation/src/AnimPose.cpp @@ -76,3 +76,5 @@ AnimPose::operator glm::mat4() const { return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f), glm::vec4(zAxis, 0.0f), glm::vec4(_trans, 1.0f)); } + + diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 27ba7a38a4..daa1c2618f 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1732,6 +1732,14 @@ glm::mat4 Rig::getJointTransform(int jointIndex) const { } } +AnimPose Rig::getJointPose(int jointIndex) const { + if (isIndexValid(jointIndex)) { + return _internalPoseSet._absolutePoses[jointIndex]; + } else { + return AnimPose::identity; + } +} + void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { const AnimPose geometryToRigPose(_geometryToRigTransform); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 2b276386a0..6968d7c3af 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -164,6 +164,7 @@ public: // rig space glm::mat4 getJointTransform(int jointIndex) const; + AnimPose getJointPose(int jointIndex) const; // Start or stop animations as needed. void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index af86499101..579910d9f7 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -782,7 +782,7 @@ void AudioClient::selectAudioFormat(const QString& selectedCodecName) { _selectedCodecName = selectedCodecName; - qCDebug(audioclient) << "Selected Codec:" << _selectedCodecName; + qCDebug(audioclient) << "Selected Codec:" << _selectedCodecName << "isStereoInput:" << _isStereoInput; // release any old codec encoder/decoder first... if (_codec && _encoder) { @@ -797,7 +797,7 @@ void AudioClient::selectAudioFormat(const QString& selectedCodecName) { if (_selectedCodecName == plugin->getName()) { _codec = plugin; _receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO); - _encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO); + _encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, _isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); qCDebug(audioclient) << "Selected Codec Plugin:" << _codec.get(); break; } @@ -1079,7 +1079,7 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) { encodedBuffer = audioBuffer; } - emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, + emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, _isStereoInput, audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale, packetType, _selectedCodecName); _stats.sentPacket(); @@ -1382,7 +1382,16 @@ void AudioClient::setIsStereoInput(bool isStereoInput) { _desiredInputFormat.setChannelCount(1); } - // change in channel count for desired input format, restart the input device + // restart the codec + if (_codec) { + if (_encoder) { + _codec->releaseEncoder(_encoder); + } + _encoder = _codec->createEncoder(AudioConstants::SAMPLE_RATE, _isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); + } + qCDebug(audioclient) << "Reset Codec:" << _selectedCodecName << "isStereoInput:" << _isStereoInput; + + // restart the input device switchInputToAudioDevice(_inputDeviceInfo); } } @@ -1418,7 +1427,7 @@ void AudioClient::outputFormatChanged() { _receivedAudioStream.outputFormatChanged(_outputFormat.sampleRate(), OUTPUT_CHANNEL_COUNT); } -bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo, bool isShutdownRequest) { +bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest) { qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << inputDeviceInfo.deviceName() << "]"; bool supportedFormat = false; @@ -1601,7 +1610,7 @@ void AudioClient::outputNotify() { } } -bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo, bool isShutdownRequest) { +bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceInfo, bool isShutdownRequest) { qCDebug(audioclient) << "AudioClient::switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]"; bool supportedFormat = false; diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 0ceb9c4dc3..0643b8e52a 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -378,8 +378,8 @@ private: void handleLocalEchoAndReverb(QByteArray& inputByteArray); - bool switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo, bool isShutdownRequest = false); - bool switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo, bool isShutdownRequest = false); + bool switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest = false); + bool switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceInfo, bool isShutdownRequest = false); // Callback acceleration dependent calculations int calculateNumberOfInputCallbackBytes(const QAudioFormat& format) const; diff --git a/libraries/audio/src/AbstractAudioInterface.cpp b/libraries/audio/src/AbstractAudioInterface.cpp index 4def97596f..376ecddd34 100644 --- a/libraries/audio/src/AbstractAudioInterface.cpp +++ b/libraries/audio/src/AbstractAudioInterface.cpp @@ -19,7 +19,7 @@ #include "AudioConstants.h" -void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, +void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, bool isStereo, const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale, PacketType packetType, QString codecName) { static std::mutex _mutex; @@ -30,9 +30,6 @@ void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes Locker lock(_mutex); auto audioPacket = NLPacket::create(packetType); - // FIXME - this is not a good way to determine stereoness with codecs.... - quint8 isStereo = bytes == AudioConstants::NETWORK_FRAME_BYTES_STEREO ? 1 : 0; - // write sequence number auto sequence = sequenceNumber++; audioPacket->writePrimitive(sequence); @@ -48,7 +45,8 @@ void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes audioPacket->writePrimitive(numSilentSamples); } else { // set the mono/stereo byte - audioPacket->writePrimitive(isStereo); + quint8 channelFlag = isStereo ? 1 : 0; + audioPacket->writePrimitive(channelFlag); } // pack the three float positions diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index 8b48b55206..37731c31f7 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -29,7 +29,7 @@ class AbstractAudioInterface : public QObject { public: AbstractAudioInterface(QObject* parent = 0) : QObject(parent) {}; - static void emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, + static void emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, bool isStereo, const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale, PacketType packetType, QString codecName = QString("")); diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index a3e6cd4341..2059487426 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -26,7 +26,7 @@ static std::weak_ptr _texturedPipeline; // FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 11 to avoid collisions static int32_t PARTICLE_UNIFORM_SLOT { 11 }; -static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key) { +static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key, gpu::Batch& batch) { auto texturedPipeline = _texturedPipeline.lock(); if (!texturedPipeline) { auto state = std::make_shared(); @@ -40,10 +40,13 @@ static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, co auto fragShader = gpu::Shader::createPixel(std::string(textured_particle_frag)); auto program = gpu::Shader::createProgram(vertShader, fragShader); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("particleBuffer"), PARTICLE_UNIFORM_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); _texturedPipeline = texturedPipeline = gpu::Pipeline::create(program, state); + + batch.runLambda([program] { + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("particleBuffer"), PARTICLE_UNIFORM_SLOT)); + gpu::Shader::makeProgram(*program, slotBindings); + }); } return std::make_shared(texturedPipeline, nullptr, nullptr, nullptr); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index b362721cde..4d223669b4 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -46,7 +46,7 @@ struct PolyLineUniforms { glm::vec3 color; }; -static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key) { +static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key, gpu::Batch& batch) { if (!polylinePipeline) { auto VS = gpu::Shader::createVertex(std::string(paintStroke_vert)); auto PS = gpu::Shader::createPixel(std::string(paintStroke_frag)); @@ -56,15 +56,21 @@ static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlum auto fadePS = gpu::Shader::createPixel(std::string(paintStroke_fade_frag)); gpu::ShaderPointer fadeProgram = gpu::Shader::createProgram(fadeVS, fadePS); #endif - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), PAINTSTROKE_TEXTURE_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("polyLineBuffer"), PAINTSTROKE_UNIFORM_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); + batch.runLambda([program #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT - slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), PAINTSTROKE_TEXTURE_SLOT + 1)); - slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), PAINTSTROKE_UNIFORM_SLOT + 1)); - gpu::Shader::makeProgram(*fadeProgram, slotBindings); + , fadeProgram #endif + ] { + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), PAINTSTROKE_TEXTURE_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("polyLineBuffer"), PAINTSTROKE_UNIFORM_SLOT)); + gpu::Shader::makeProgram(*program, slotBindings); +#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT + slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), PAINTSTROKE_TEXTURE_SLOT + 1)); + slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), PAINTSTROKE_UNIFORM_SLOT + 1)); + gpu::Shader::makeProgram(*fadeProgram, slotBindings); +#endif + }); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(true, true, gpu::LESS_EQUAL); PrepareStencil::testMask(*state); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index ad0202457e..b693ea007d 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1457,7 +1457,7 @@ static gpu::PipelinePointer _pipelines[2]; static gpu::PipelinePointer _wireframePipelines[2]; static gpu::Stream::FormatPointer _vertexFormat; -ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key) { +ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key, gpu::Batch& batch) { if (!_pipelines[0]) { gpu::ShaderPointer vertexShaders[2] = { gpu::Shader::createVertex(std::string(polyvox_vert)), gpu::Shader::createVertex(std::string(polyvox_fade_vert)) }; gpu::ShaderPointer pixelShaders[2] = { gpu::Shader::createPixel(std::string(polyvox_frag)), gpu::Shader::createPixel(std::string(polyvox_fade_frag)) }; @@ -1485,7 +1485,10 @@ ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const Sha // Two sets of pipelines: normal and fading for (auto i = 0; i < 2; i++) { gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShaders[i], pixelShaders[i]); - gpu::Shader::makeProgram(*program, slotBindings); + + batch.runLambda([program, slotBindings] { + gpu::Shader::makeProgram(*program, slotBindings); + }); _pipelines[i] = gpu::Pipeline::create(program, state); _wireframePipelines[i] = gpu::Pipeline::create(program, wireframeState); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index d0dabbf5b6..c3d04dc7ad 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -185,11 +185,9 @@ public: DEFINE_PROPERTY_REF(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t, PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE); DEFINE_PROPERTY_REF(PROP_NAME, Name, name, QString, ENTITY_ITEM_DEFAULT_NAME); - DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); - DEFINE_PROPERTY_REF_ENUM(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); - DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); - - // This is the default mode for zone creation + DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); + DEFINE_PROPERTY_REF_ENUM(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); + DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ae243623f6..907426c922 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -39,11 +39,9 @@ #include "EntityEditFilters.h" #include "EntityDynamicFactoryInterface.h" - static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour - // combines the ray cast arguments into a single object class RayArgs { public: @@ -2237,10 +2235,14 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer } bool EntityTree::readFromMap(QVariantMap& map) { + // These are needed to deal with older content (before adding inheritance modes) + int contentVersion = map["Version"].toInt(); + bool needsConversion = (contentVersion < (int)EntityVersion::ZoneLightInheritModes); + // map will have a top-level list keyed as "Entities". This will be extracted // and iterated over. Each member of this list is converted to a QVariantMap, then // to a QScriptValue, and then to EntityItemProperties. These properties are used - // to add the new entity to the EnitytTree. + // to add the new entity to the EntityTree. QVariantList entitiesQList = map["Entities"].toList(); QScriptEngine scriptEngine; @@ -2281,29 +2283,30 @@ bool EntityTree::readFromMap(QVariantMap& map) { properties.setOwningAvatarID(myNodeID); } - // TEMPORARY fix for older content not containing these fields in the zones - if (properties.getType() == EntityTypes::EntityType::Zone) { - if (!entityMap.contains("keyLightMode")) { - properties.setKeyLightMode(COMPONENT_MODE_ENABLED); + // Fix for older content not containing these fields in the zones + if (needsConversion && (properties.getType() == EntityTypes::EntityType::Zone)) { + // The ambient URL has been moved from "keyLight" to "ambientLight" + if (entityMap.contains("keyLight")) { + QVariantMap keyLightObject = entityMap["keyLight"].toMap(); + properties.getAmbientLight().setAmbientURL(keyLightObject["ambientURL"].toString()); } - if (!entityMap.contains("skyboxMode")) { - if (entityMap.contains("backgroundMode") && (entityMap["backgroundMode"].toString() == "nothing")) { - properties.setSkyboxMode(COMPONENT_MODE_INHERIT); - } else { - // Either the background mode field is missing (shouldn't happen) or the background mode is "skybox" - properties.setSkyboxMode(COMPONENT_MODE_ENABLED); - - // Copy the skybox URL if the ambient URL is empty, as this is the legacy behaviour - if (properties.getAmbientLight().getAmbientURL() == "") { - properties.getAmbientLight().setAmbientURL(properties.getSkybox().getURL()); - } + // The background should be enabled if the mode is skybox + // Note that if the values are default then they are not stored in the JSON file + if (entityMap.contains("backgroundMode") && (entityMap["backgroundMode"].toString() == "skybox")) { + properties.setSkyboxMode(COMPONENT_MODE_ENABLED); + + // Copy the skybox URL if the ambient URL is empty, as this is the legacy behaviour + if (properties.getAmbientLight().getAmbientURL() == "") { + properties.getAmbientLight().setAmbientURL(properties.getSkybox().getURL()); } + } else { + properties.setSkyboxMode(COMPONENT_MODE_INHERIT); } - if (!entityMap.contains("ambientLightMode")) { - properties.setAmbientLightMode(COMPONENT_MODE_ENABLED); - } + // The legacy version had no keylight/ambient modes - these are always on + properties.setKeyLightMode(COMPONENT_MODE_ENABLED); + properties.setAmbientLightMode(COMPONENT_MODE_ENABLED); } EntityItemPointer entity = addEntity(entityItemID, properties); @@ -2312,6 +2315,7 @@ bool EntityTree::readFromMap(QVariantMap& map) { success = false; } } + return success; } diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index 2150463065..50d40c35ac 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -116,6 +116,7 @@ public: int jointIndex; glm::mat4 inverseBindMatrix; + Transform inverseBindTransform; }; const int MAX_NUM_PIXELS_FOR_FBX_TEXTURE = 2048 * 2048; @@ -225,7 +226,7 @@ public: QVector texCoords; QVector texCoords1; QVector clusterIndices; - QVector clusterWeights; + QVector clusterWeights; QVector originalIndices; QVector clusters; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 1694e31e1d..659d6dfa1e 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1675,6 +1675,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS fbxCluster.jointIndex = 0; } fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform; + fbxCluster.inverseBindTransform = Transform(fbxCluster.inverseBindMatrix); extracted.mesh.clusters.append(fbxCluster); // override the bind rotation with the transform link @@ -1789,9 +1790,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } if (totalWeight > 0.0f) { const float ALMOST_HALF = 0.499f; - float weightScalingFactor = (float)(UINT8_MAX) / totalWeight; + float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { - extracted.mesh.clusterWeights[k] = (uint8_t)(weightScalingFactor * weightAccumulators[k] + ALMOST_HALF); + extracted.mesh.clusterWeights[k] = (uint16_t)(weightScalingFactor * weightAccumulators[k] + ALMOST_HALF); } } } diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index b9549e2c4e..309c421052 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -624,7 +624,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // we need 16 bits instead of just 8 for clusterIndices clusterIndicesSize *= 2; } - const int clusterWeightsSize = fbxMesh.clusterWeights.size() * sizeof(uint8_t); + + const int clusterWeightsSize = fbxMesh.clusterWeights.size() * sizeof(uint16_t); // Normals and tangents are interleaved const int normalsOffset = 0; @@ -759,7 +760,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { if (clusterWeightsSize) { mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, model::BufferView(attribBuffer, clusterWeightsOffset, clusterWeightsSize, - gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::XYZW))); + gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW))); } diff --git a/libraries/gl/src/gl/GLShaders.cpp b/libraries/gl/src/gl/GLShaders.cpp index 8ef0198676..017c92b71c 100644 --- a/libraries/gl/src/gl/GLShaders.cpp +++ b/libraries/gl/src/gl/GLShaders.cpp @@ -63,12 +63,17 @@ namespace gl { } */ - qCWarning(glLogging) << "GLShader::compileShader - failed to compile the gl shader object:"; + qCCritical(glLogging) << "GLShader::compileShader - failed to compile the gl shader object:"; + int lineNumber = 0; for (auto s : srcstr) { - qCWarning(glLogging) << s; + QString str(s); + QStringList lines = str.split("\n"); + for (auto& line : lines) { + qCCritical(glLogging).noquote() << QString("%1: %2").arg(lineNumber++, 5, 10, QChar('0')).arg(line); + } } - qCWarning(glLogging) << "GLShader::compileShader - errors:"; - qCWarning(glLogging) << temp; + qCCritical(glLogging) << "GLShader::compileShader - errors:"; + qCCritical(glLogging) << temp; error = std::string(temp); delete[] temp; diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendOutput.cpp index b38b2b5586..1552aff563 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLBackendOutput.cpp @@ -68,11 +68,10 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { if (masks & Framebuffer::BUFFER_STENCIL) { glClearStencil(stencil); glmask |= GL_STENCIL_BUFFER_BIT; - cacheStencilMask = _pipeline._stateCache.stencilActivation.getWriteMaskFront(); if (cacheStencilMask != 0xFF) { restoreStencilMask = true; - glStencilMask( 0xFF); + glStencilMask(0xFF); } } diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index fca220c224..79f08ea5cc 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -269,6 +269,10 @@ public: void _glColor4f(float red, float green, float blue, float alpha); + // Maybe useful but shoudln't be public. Please convince me otherwise + // Well porting to gles i need it... + void runLambda(std::function f); + enum Command { COMMAND_draw = 0, COMMAND_drawIndexed, @@ -497,8 +501,7 @@ protected: void startNamedCall(const std::string& name); void stopNamedCall(); - // Maybe useful but shoudln't be public. Please convince me otherwise - void runLambda(std::function f); + void captureDrawCallInfoImpl(); }; diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index fd3061afa5..b87d30c9d1 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -89,12 +89,14 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky auto skyFS = gpu::Shader::createPixel(std::string(skybox_frag)); auto skyShader = gpu::Shader::createProgram(skyVS, skyFS); - gpu::Shader::BindingSet bindings; - bindings.insert(gpu::Shader::Binding(std::string("cubeMap"), SKYBOX_SKYMAP_SLOT)); - bindings.insert(gpu::Shader::Binding(std::string("skyboxBuffer"), SKYBOX_CONSTANTS_SLOT)); - if (!gpu::Shader::makeProgram(*skyShader, bindings)) { + batch.runLambda([skyShader] { + gpu::Shader::BindingSet bindings; + bindings.insert(gpu::Shader::Binding(std::string("cubeMap"), SKYBOX_SKYMAP_SLOT)); + bindings.insert(gpu::Shader::Binding(std::string("skyboxBuffer"), SKYBOX_CONSTANTS_SLOT)); + if (!gpu::Shader::makeProgram(*skyShader, bindings)) { - } + } + }); auto skyState = std::make_shared(); // Must match PrepareStencil::STENCIL_BACKGROUND diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 7eafbbccf5..acc1ff01db 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -196,13 +196,15 @@ void sendWrongProtocolVersionsSignature(bool sendWrongVersion); /// for debuggin uint qHash(const PacketType& key, uint seed); QDebug operator<<(QDebug debug, const PacketType& type); +// Due to the different legacy behaviour, we need special processing for domains that were created before +// the zone inheritance modes were added. These have version numbers up to 80 enum class EntityVersion : PacketVersion { StrokeColorProperty = 0, HasDynamicOwnershipTests, HazeEffect, StaticCertJsonVersionOne, OwnershipChallengeFix, - ZoneLightInheritModes, + ZoneLightInheritModes = 82, ZoneStageRemoved }; diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp index 9de973480a..3d213840dd 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp @@ -20,16 +20,16 @@ using namespace render; 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::updateClusterBuffer(const std::vector& clusterMatrices, const std::vector& cauterizedClusterMatrices) { - ModelMeshPartPayload::updateClusterBuffer(clusterMatrices); +void CauterizedMeshPartPayload::updateClusterBuffer(const std::vector& clusterTransforms, const std::vector& cauterizedClusterTransforms) { + ModelMeshPartPayload::updateClusterBuffer(clusterTransforms); - if (cauterizedClusterMatrices.size() > 1) { + if (cauterizedClusterTransforms.size() > 1) { if (!_cauterizedClusterBuffer) { - _cauterizedClusterBuffer = std::make_shared(cauterizedClusterMatrices.size() * sizeof(glm::mat4), - (const gpu::Byte*) cauterizedClusterMatrices.data()); + _cauterizedClusterBuffer = std::make_shared(cauterizedClusterTransforms.size() * sizeof(TransformType), + (const gpu::Byte*) cauterizedClusterTransforms.data()); } else { - _cauterizedClusterBuffer->setSubData(0, cauterizedClusterMatrices.size() * sizeof(glm::mat4), - (const gpu::Byte*) cauterizedClusterMatrices.data()); + _cauterizedClusterBuffer->setSubData(0, cauterizedClusterTransforms.size() * sizeof(TransformType), + (const gpu::Byte*) cauterizedClusterTransforms.data()); } } } diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.h b/libraries/render-utils/src/CauterizedMeshPartPayload.h index 44eddc6e31..2337632047 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.h +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.h @@ -15,7 +15,13 @@ class CauterizedMeshPartPayload : public ModelMeshPartPayload { public: CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform); - void updateClusterBuffer(const std::vector& clusterMatrices, const std::vector& cauterizedClusterMatrices); +#if defined(SKIN_DQ) + using TransformType = Model::TransformDualQuaternion; +#else + using TransformType = glm::mat4; +#endif + + void updateClusterBuffer(const std::vector& clusterTransforms, const std::vector& cauterizedClusterTransforms); void updateTransformForCauterizedMesh(const Transform& renderTransform); diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index dbb82ab638..e3f26a43d8 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -9,13 +9,13 @@ #include "CauterizedModel.h" #include +#include #include "AbstractViewStateInterface.h" #include "MeshPartPayload.h" #include "CauterizedMeshPartPayload.h" #include "RenderUtilsLogging.h" - CauterizedModel::CauterizedModel(QObject* parent) : Model(parent) { } @@ -35,7 +35,7 @@ bool CauterizedModel::updateGeometry() { const FBXGeometry& fbxGeometry = getFBXGeometry(); foreach (const FBXMesh& mesh, fbxGeometry.meshes) { Model::MeshState state; - state.clusterMatrices.resize(mesh.clusters.size()); + state.clusterTransforms.resize(mesh.clusters.size()); _cauterizeMeshStates.append(state); } } @@ -109,30 +109,52 @@ 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); +#if defined(SKIN_DQ) + auto jointPose = _rig.getJointPose(cluster.jointIndex); + Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); + Transform clusterTransform; + Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); + state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform); +#else auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]); +#endif } } // as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty. if (!_cauterizeBoneSet.empty()) { +#if defined(SKIN_DQ) + AnimPose cauterizePose = _rig.getJointPose(geometry.neckJointIndex); + cauterizePose.scale() = glm::vec3(0.0001f, 0.0001f, 0.0001f); +#else static const glm::mat4 zeroScale( - 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, 0.0f), + glm::vec4(0.0001f, 0.0f, 0.0f, 0.0f), + glm::vec4(0.0f, 0.0001f, 0.0f, 0.0f), + glm::vec4(0.0f, 0.0f, 0.0001f, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); auto cauterizeMatrix = _rig.getJointTransform(geometry.neckJointIndex) * zeroScale; - +#endif 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); - if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) { - jointMatrix = cauterizeMatrix; + + if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) { + // not cauterized so just copy the value from the non-cauterized version. + state.clusterTransforms[j] = _meshStates[i].clusterTransforms[j]; + } else { +#if defined(SKIN_DQ) + Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans()); + Transform clusterTransform; + Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); + state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform); +#else + glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]); +#endif } - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); } } } @@ -189,24 +211,38 @@ void CauterizedModel::updateRenderItems() { auto itemID = self->_modelMeshRenderItemIDs[i]; auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; - auto clusterMatrices(self->getMeshState(meshIndex).clusterMatrices); - auto clusterMatricesCauterized(self->getCauterizeMeshState(meshIndex).clusterMatrices); + auto clusterTransforms(self->getMeshState(meshIndex).clusterTransforms); + auto clusterTransformsCauterized(self->getCauterizeMeshState(meshIndex).clusterTransforms); bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); - transaction.updateItem(itemID, [modelTransform, clusterMatrices, clusterMatricesCauterized, invalidatePayloadShapeKey, + transaction.updateItem(itemID, [modelTransform, clusterTransforms, clusterTransformsCauterized, invalidatePayloadShapeKey, isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, enableCauterization](CauterizedMeshPartPayload& data) { - data.updateClusterBuffer(clusterMatrices, clusterMatricesCauterized); + data.updateClusterBuffer(clusterTransforms, clusterTransformsCauterized); Transform renderTransform = modelTransform; - if (clusterMatrices.size() == 1) { - renderTransform = modelTransform.worldTransform(Transform(clusterMatrices[0])); + if (clusterTransforms.size() == 1) { +#if defined(SKIN_DQ) + Transform transform(clusterTransforms[0].getRotation(), + clusterTransforms[0].getScale(), + clusterTransforms[0].getTranslation()); + renderTransform = modelTransform.worldTransform(transform); +#else + renderTransform = modelTransform.worldTransform(Transform(clusterTransforms[0])); +#endif } data.updateTransformForSkinnedMesh(renderTransform, modelTransform); renderTransform = modelTransform; - if (clusterMatricesCauterized.size() == 1) { - renderTransform = modelTransform.worldTransform(Transform(clusterMatricesCauterized[0])); + if (clusterTransformsCauterized.size() == 1) { +#if defined(SKIN_DQ) + Transform transform(clusterTransforms[0].getRotation(), + clusterTransforms[0].getScale(), + clusterTransforms[0].getTranslation()); + renderTransform = modelTransform.worldTransform(Transform(transform)); +#else + renderTransform = modelTransform.worldTransform(Transform(clusterTransformsCauterized[0])); +#endif } data.updateTransformForCauterizedMesh(renderTransform); diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 2616d08600..d5a3a92f8f 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -639,7 +639,7 @@ render::ShapePipelinePointer GeometryCache::_simpleWirePipeline; uint8_t GeometryCache::CUSTOM_PIPELINE_NUMBER = 0; -render::ShapePipelinePointer GeometryCache::shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key) { +render::ShapePipelinePointer GeometryCache::shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key, gpu::Batch& batch) { initializeShapePipelines(); if (key.isWireframe()) { @@ -2001,11 +2001,11 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const } void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { - if (!_standardDrawPipeline) { + static std::once_flag once; + std::call_once(once, [&]() { auto vs = gpu::Shader::createVertex(std::string(standardTransformPNTC_vert)); auto ps = gpu::Shader::createPixel(std::string(standardDrawTexture_frag)); auto program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram((*program)); auto state = std::make_shared(); @@ -2021,9 +2021,15 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { auto noBlendPS = gpu::StandardShaderLib::getDrawTextureOpaquePS(); auto programNoBlend = gpu::Shader::createProgram(vs, noBlendPS); - gpu::Shader::makeProgram((*programNoBlend)); + _standardDrawPipelineNoBlend = gpu::Pipeline::create(programNoBlend, stateNoBlend); - } + + batch.runLambda([program, programNoBlend] { + gpu::Shader::makeProgram((*program)); + gpu::Shader::makeProgram((*programNoBlend)); + }); + }); + if (noBlend) { batch.setPipeline(_standardDrawPipelineNoBlend); } else { diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 0585cc9e55..a84a80fe74 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -158,7 +158,7 @@ public: static void computeSimpleHullPointListForShape(int entityShape, const glm::vec3 &entityExtents, QVector &outPointList); static uint8_t CUSTOM_PIPELINE_NUMBER; - static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key); + static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key, gpu::Batch& batch); static void registerShapePipeline() { if (!CUSTOM_PIPELINE_NUMBER) { CUSTOM_PIPELINE_NUMBER = render::ShapePipeline::registerCustomShapePipelineFactory(shapePipelineFactory); diff --git a/libraries/render-utils/src/LightingModel.h b/libraries/render-utils/src/LightingModel.h index e058b10921..d7d2f2bba1 100644 --- a/libraries/render-utils/src/LightingModel.h +++ b/libraries/render-utils/src/LightingModel.h @@ -96,7 +96,7 @@ protected: float enableObscurance{ 1.0f }; - float enableMaterialTexturing { 1.0f }; + float enableMaterialTexturing { 0.0f }; float enableWireframe { 0.0f }; // false by default Parameters() {} @@ -148,7 +148,7 @@ public: bool enableSpecular{ true }; bool enableAlbedo{ true }; - bool enableMaterialTexturing { true }; + bool enableMaterialTexturing { false }; bool enableAmbientLight{ true }; bool enableDirectionalLight{ true }; diff --git a/libraries/render-utils/src/MaterialTextures.slh b/libraries/render-utils/src/MaterialTextures.slh index 6c77fc4a91..40709b3668 100644 --- a/libraries/render-utils/src/MaterialTextures.slh +++ b/libraries/render-utils/src/MaterialTextures.slh @@ -17,7 +17,9 @@ const int MAX_TEXCOORDS = 2; struct TexMapArray { - mat4 _texcoordTransforms[MAX_TEXCOORDS]; +// mat4 _texcoordTransforms[MAX_TEXCOORDS]; + mat4 _texcoordTransforms0; + mat4 _texcoordTransforms1; vec4 _lightmapParams; }; @@ -31,13 +33,13 @@ TexMapArray getTexMapArray() { <@func evalTexMapArrayTexcoord0(texMapArray, inTexcoord0, outTexcoord0)@> { - <$outTexcoord0$> = (<$texMapArray$>._texcoordTransforms[0] * vec4(<$inTexcoord0$>.st, 0.0, 1.0)).st; + <$outTexcoord0$> = (<$texMapArray$>._texcoordTransforms0 * vec4(<$inTexcoord0$>.st, 0.0, 1.0)).st; } <@endfunc@> <@func evalTexMapArrayTexcoord1(texMapArray, inTexcoord1, outTexcoord1)@> { - <$outTexcoord1$> = (<$texMapArray$>._texcoordTransforms[1] * vec4(<$inTexcoord1$>.st, 0.0, 1.0)).st; + <$outTexcoord1$> = (<$texMapArray$>._texcoordTransforms1 * vec4(<$inTexcoord1$>.st, 0.0, 1.0)).st; } <@endfunc@> diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index cb0a43b1e9..d6ab2ff416 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -12,6 +12,7 @@ #include "MeshPartPayload.h" #include +#include #include "DeferredLightingEffect.h" @@ -325,12 +326,20 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in const Model::MeshState& state = model->getMeshState(_meshIndex); updateMeshPart(modelMesh, partIndex); - computeAdjustedLocalBound(state.clusterMatrices); + computeAdjustedLocalBound(state.clusterTransforms); updateTransform(transform, offsetTransform); Transform renderTransform = transform; - if (state.clusterMatrices.size() == 1) { - renderTransform = transform.worldTransform(Transform(state.clusterMatrices[0])); + if (state.clusterTransforms.size() == 1) { +#if defined(SKIN_DQ) + Transform transform(state.clusterTransforms[0].getRotation(), + state.clusterTransforms[0].getScale(), + state.clusterTransforms[0].getTranslation()); + renderTransform = transform.worldTransform(Transform(transform)); +#else + renderTransform = transform.worldTransform(Transform(state.clusterTransforms[0])); +#endif + } updateTransformForSkinnedMesh(renderTransform, transform); @@ -360,17 +369,16 @@ void ModelMeshPartPayload::notifyLocationChanged() { } - -void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clusterMatrices) { +void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clusterTransforms) { // Once computed the cluster matrices, update the buffer(s) - if (clusterMatrices.size() > 1) { + if (clusterTransforms.size() > 1) { if (!_clusterBuffer) { - _clusterBuffer = std::make_shared(clusterMatrices.size() * sizeof(glm::mat4), - (const gpu::Byte*) clusterMatrices.data()); + _clusterBuffer = std::make_shared(clusterTransforms.size() * sizeof(TransformType), + (const gpu::Byte*) clusterTransforms.data()); } else { - _clusterBuffer->setSubData(0, clusterMatrices.size() * sizeof(glm::mat4), - (const gpu::Byte*) clusterMatrices.data()); + _clusterBuffer->setSubData(0, clusterTransforms.size() * sizeof(TransformType), + (const gpu::Byte*) clusterTransforms.data()); } } } @@ -530,13 +538,29 @@ void ModelMeshPartPayload::render(RenderArgs* args) { args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE; } -void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterMatrices) { + +void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterTransforms) { _adjustedLocalBound = _localBound; - if (clusterMatrices.size() > 0) { - _adjustedLocalBound.transform(clusterMatrices[0]); - for (int i = 1; i < (int)clusterMatrices.size(); ++i) { + if (clusterTransforms.size() > 0) { +#if defined(SKIN_DQ) + Transform rootTransform(clusterTransforms[0].getRotation(), + clusterTransforms[0].getScale(), + clusterTransforms[0].getTranslation()); + _adjustedLocalBound.transform(rootTransform); +#else + _adjustedLocalBound.transform(clusterTransforms[0]); +#endif + + for (int i = 1; i < (int)clusterTransforms.size(); ++i) { AABox clusterBound = _localBound; - clusterBound.transform(clusterMatrices[i]); +#if defined(SKIN_DQ) + Transform transform(clusterTransforms[i].getRotation(), + clusterTransforms[i].getScale(), + clusterTransforms[i].getTranslation()); + clusterBound.transform(transform); +#else + clusterBound.transform(clusterTransforms[i]); +#endif _adjustedLocalBound += clusterBound; } } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index fb55883101..8d36395610 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -87,7 +87,14 @@ public: typedef Payload::DataPointer Pointer; void notifyLocationChanged() override; - void updateClusterBuffer(const std::vector& clusterMatrices); + +#if defined(SKIN_DQ) + using TransformType = Model::TransformDualQuaternion; +#else + using TransformType = glm::mat4; +#endif + + void updateClusterBuffer(const std::vector& clusterTransforms); void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform); // Render Item interface @@ -104,7 +111,7 @@ public: void bindMesh(gpu::Batch& batch) override; void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override; - void computeAdjustedLocalBound(const std::vector& clusterMatrices); + void computeAdjustedLocalBound(const std::vector& clusterTransforms); gpu::BufferPointer _clusterBuffer; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 539f0421b0..9c1c579341 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -27,6 +27,7 @@ #include #include +#include #include @@ -269,16 +270,24 @@ void Model::updateRenderItems() { auto itemID = self->_modelMeshRenderItemIDs[i]; auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; - auto clusterMatrices(self->getMeshState(meshIndex).clusterMatrices); + auto clusterTransforms(self->getMeshState(meshIndex).clusterTransforms); bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); - transaction.updateItem(itemID, [modelTransform, clusterMatrices, invalidatePayloadShapeKey, + transaction.updateItem(itemID, [modelTransform, clusterTransforms, invalidatePayloadShapeKey, isWireframe, isVisible, isLayeredInFront, isLayeredInHUD](ModelMeshPartPayload& data) { - data.updateClusterBuffer(clusterMatrices); + data.updateClusterBuffer(clusterTransforms); + Transform renderTransform = modelTransform; - if (clusterMatrices.size() == 1) { - renderTransform = modelTransform.worldTransform(Transform(clusterMatrices[0])); + if (clusterTransforms.size() == 1) { +#if defined(SKIN_DQ) + Transform transform(clusterTransforms[0].getRotation(), + clusterTransforms[0].getScale(), + clusterTransforms[0].getTranslation()); + renderTransform = modelTransform.worldTransform(Transform(transform)); +#else + renderTransform = modelTransform.worldTransform(Transform(clusterTransforms[0])); +#endif } data.updateTransformForSkinnedMesh(renderTransform, modelTransform); @@ -359,7 +368,7 @@ bool Model::updateGeometry() { const FBXGeometry& fbxGeometry = getFBXGeometry(); foreach (const FBXMesh& mesh, fbxGeometry.meshes) { MeshState state; - state.clusterMatrices.resize(mesh.clusters.size()); + state.clusterTransforms.resize(mesh.clusters.size()); _meshStates.push_back(state); // Note: we add empty buffers for meshes that lack blendshapes so we can access the buffers by index @@ -1211,7 +1220,7 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { void Model::computeMeshPartLocalBounds() { for (auto& part : _modelMeshRenderItems) { const Model::MeshState& state = _meshStates.at(part->_meshIndex); - part->computeAdjustedLocalBound(state.clusterMatrices); + part->computeAdjustedLocalBound(state.clusterTransforms); } } @@ -1222,6 +1231,7 @@ void Model::updateClusterMatrices() { if (!_needsUpdateClusterMatrices || !isLoaded()) { return; } + _needsUpdateClusterMatrices = false; const FBXGeometry& geometry = getFBXGeometry(); for (int i = 0; i < (int) _meshStates.size(); i++) { @@ -1229,8 +1239,16 @@ 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); +#if defined(SKIN_DQ) + auto jointPose = _rig.getJointPose(cluster.jointIndex); + Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); + Transform clusterTransform; + Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); + state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform); +#else auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]); +#endif } } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 77ed629962..623f869666 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -30,11 +30,15 @@ #include #include #include +#include #include "GeometryCache.h" #include "TextureCache.h" #include "Rig.h" +// Use dual quaternion skinning! +// Must match define in Skinning.slh +#define SKIN_DQ class AbstractViewStateInterface; class QScriptEngine; @@ -246,9 +250,46 @@ public: int getRenderInfoDrawCalls() const { return _renderInfoDrawCalls; } bool getRenderInfoHasTransparent() const { return _renderInfoHasTransparent; } + +#if defined(SKIN_DQ) + class TransformDualQuaternion { + public: + TransformDualQuaternion() {} + TransformDualQuaternion(const glm::mat4& m) { + AnimPose p(m); + _scale.x = p.scale().x; + _scale.y = p.scale().y; + _scale.z = p.scale().z; + _dq = DualQuaternion(p.rot(), p.trans()); + } + TransformDualQuaternion(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) { + _scale.x = scale.x; + _scale.y = scale.y; + _scale.z = scale.z; + _dq = DualQuaternion(rot, trans); + } + TransformDualQuaternion(const Transform& transform) { + _scale = glm::vec4(transform.getScale(), 0.0f); + _dq = DualQuaternion(transform.getRotation(), transform.getTranslation()); + } + glm::vec3 getScale() const { return glm::vec3(_scale); } + glm::quat getRotation() const { return _dq.getRotation(); } + glm::vec3 getTranslation() const { return _dq.getTranslation(); } + glm::mat4 getMatrix() const { return createMatFromScaleQuatAndPos(getScale(), getRotation(), getTranslation()); }; + protected: + glm::vec4 _scale { 1.0f, 1.0f, 1.0f, 0.0f }; + DualQuaternion _dq; + glm::vec4 _padding; + }; +#endif + class MeshState { public: - std::vector clusterMatrices; +#if defined(SKIN_DQ) + std::vector clusterTransforms; +#else + std::vector clusterTransforms; +#endif }; const MeshState& getMeshState(int index) { return _meshStates.at(index); } diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index b833fb47b8..d9845f3e80 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -26,6 +26,7 @@ #include "FramebufferCache.h" #include "TextureCache.h" #include "RenderCommonTask.h" +#include "LightStage.h" #include "nop_frag.h" @@ -63,17 +64,21 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend // GPU jobs: Start preparing the main framebuffer const auto framebuffer = task.addJob("PrepareFramebuffer"); + task.addJob("PrepareForward", lightingModel); + // draw a stencil mask in hidden regions of the framebuffer. task.addJob("PrepareStencil", framebuffer); // Draw opaques forward - task.addJob("DrawOpaques", opaques, shapePlumber); + const auto opaqueInputs = DrawForward::Inputs(opaques, lightingModel).asVarying(); + task.addJob("DrawOpaques", opaqueInputs, shapePlumber); // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job task.addJob("DrawBackgroundDeferred", lightingModel); // Draw transparent objects forward - task.addJob("DrawTransparents", transparents, shapePlumber); + const auto transparentInputs = DrawForward::Inputs(transparents, lightingModel).asVarying(); + task.addJob("DrawTransparents", transparentInputs, shapePlumber); { // Debug the bounds of the rendered items, still look at the zbuffer @@ -125,18 +130,85 @@ void PrepareFramebuffer::run(const RenderContextPointer& renderContext, gpu::Fra batch.setFramebuffer(_framebuffer); batch.clearFramebuffer(gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH | gpu::Framebuffer::BUFFER_STENCIL, - vec4(vec3(0), 1), 1.0, 0, true); + vec4(vec3(0, 1.0, 0.0), 0), 1.0, 0, true); }); framebuffer = _framebuffer; } -void Draw::run(const RenderContextPointer& renderContext, const Inputs& items) { +enum ForwardShader_MapSlot { + DEFERRED_BUFFER_COLOR_UNIT = 0, + DEFERRED_BUFFER_NORMAL_UNIT = 1, + DEFERRED_BUFFER_EMISSIVE_UNIT = 2, + DEFERRED_BUFFER_DEPTH_UNIT = 3, + DEFERRED_BUFFER_OBSCURANCE_UNIT = 4, + SHADOW_MAP_UNIT = 5, + SKYBOX_MAP_UNIT = SHADOW_MAP_UNIT + 4, + DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, + DEFERRED_BUFFER_CURVATURE_UNIT, + DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT, + SCATTERING_LUT_UNIT, + SCATTERING_SPECULAR_UNIT, +}; +enum ForwardShader_BufferSlot { + DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT = 0, + CAMERA_CORRECTION_BUFFER_SLOT, + SCATTERING_PARAMETERS_BUFFER_SLOT, + LIGHTING_MODEL_BUFFER_SLOT = render::ShapePipeline::Slot::LIGHTING_MODEL, + LIGHT_GPU_SLOT = render::ShapePipeline::Slot::LIGHT, + LIGHT_AMBIENT_SLOT = render::ShapePipeline::Slot::LIGHT_AMBIENT_BUFFER, + HAZE_MODEL_BUFFER_SLOT = render::ShapePipeline::Slot::HAZE_MODEL, + LIGHT_INDEX_GPU_SLOT, + LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, + LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, + LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, +}; + +void PrepareForward::run(const RenderContextPointer& renderContext, const Inputs& inputs) { RenderArgs* args = renderContext->args; + const auto& lightingModel = inputs; + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + args->_batch = &batch; + + model::LightPointer keySunLight; + auto lightStage = args->_scene->getStage(); + if (lightStage) { + keySunLight = lightStage->getCurrentKeyLight(); + } + + model::LightPointer keyAmbiLight; + if (lightStage) { + keyAmbiLight = lightStage->getCurrentAmbientLight(); + } + + if (keySunLight) { + if (LIGHT_GPU_SLOT >= 0) { + batch.setUniformBuffer(LIGHT_GPU_SLOT, keySunLight->getLightSchemaBuffer()); + } + } + + if (keyAmbiLight) { + if (LIGHT_AMBIENT_SLOT >= 0) { + batch.setUniformBuffer(LIGHT_AMBIENT_SLOT, keyAmbiLight->getAmbientSchemaBuffer()); + } + + if (keyAmbiLight->getAmbientMap() && (SKYBOX_MAP_UNIT >= 0)) { + batch.setResourceTexture(SKYBOX_MAP_UNIT, keyAmbiLight->getAmbientMap()); + } + } + }); +} + +void DrawForward::run(const RenderContextPointer& renderContext, const Inputs& inputs) { + RenderArgs* args = renderContext->args; + + const auto& inItems = inputs.get0(); + const auto& lightingModel = inputs.get1(); gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; + // Setup projection glm::mat4 projMat; Transform viewMat; @@ -146,63 +218,23 @@ void Draw::run(const RenderContextPointer& renderContext, const Inputs& items) { batch.setViewTransform(viewMat); batch.setModelTransform(Transform()); + // Setup lighting model for all items; + batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + + // From the lighting model define a global shapeKey ORED with individiual keys + ShapeKey::Builder keyBuilder; + if (lightingModel->isWireframeEnabled()) { + keyBuilder.withWireframe(); + } + ShapeKey globalKey = keyBuilder.build(); + args->_globalShapeKey = globalKey._flags.to_ulong(); + // Render items - renderStateSortShapes(renderContext, _shapePlumber, items, -1); + renderStateSortShapes(renderContext, _shapePlumber, inItems, -1, globalKey); + + args->_batch = nullptr; + args->_globalShapeKey = 0; }); - args->_batch = nullptr; } -const gpu::PipelinePointer Stencil::getPipeline() { - if (!_stencilPipeline) { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(nop_frag)); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram(*program); - auto state = std::make_shared(); - state->setDepthTest(true, false, gpu::LESS_EQUAL); - PrepareStencil::drawBackground(*state); - - _stencilPipeline = gpu::Pipeline::create(program, state); - } - return _stencilPipeline; -} - -void Stencil::run(const RenderContextPointer& renderContext) { - RenderArgs* args = renderContext->args; - - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - args->_batch = &batch; - - batch.enableStereo(false); - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - batch.setPipeline(getPipeline()); - batch.draw(gpu::TRIANGLE_STRIP, 4); - }); - args->_batch = nullptr; -} - -void DrawBackground::run(const RenderContextPointer& renderContext, const Inputs& background) { - RenderArgs* args = renderContext->args; - - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - args->_batch = &batch; - - batch.enableSkybox(true); - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - // Setup projection - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); - - renderItems(renderContext, background); - }); - args->_batch = nullptr; -} \ No newline at end of file diff --git a/libraries/render-utils/src/RenderForwardTask.h b/libraries/render-utils/src/RenderForwardTask.h index 63d087b3c5..22e75d4bdc 100755 --- a/libraries/render-utils/src/RenderForwardTask.h +++ b/libraries/render-utils/src/RenderForwardTask.h @@ -38,37 +38,28 @@ private: gpu::FramebufferPointer _framebuffer; }; -class Draw { +class PrepareForward { public: - using Inputs = render::ItemBounds; - using JobModel = render::Job::ModelI; + using Inputs = LightingModelPointer; + using JobModel = render::Job::ModelI; - Draw(const render::ShapePlumberPointer& shapePlumber) : _shapePlumber(shapePlumber) {} void run(const render::RenderContextPointer& renderContext, - const Inputs& items); + const Inputs& inputs); + +private: +}; + +class DrawForward{ +public: + using Inputs = render::VaryingSet2; + using JobModel = render::Job::ModelI; + + DrawForward(const render::ShapePlumberPointer& shapePlumber) : _shapePlumber(shapePlumber) {} + void run(const render::RenderContextPointer& renderContext, + const Inputs& inputs); private: render::ShapePlumberPointer _shapePlumber; }; -class Stencil { -public: - using JobModel = render::Job::Model; - - void run(const render::RenderContextPointer& renderContext); - -private: - const gpu::PipelinePointer getPipeline(); - gpu::PipelinePointer _stencilPipeline; -}; - -class DrawBackground { -public: - using Inputs = render::ItemBounds; - using JobModel = render::Job::ModelI; - - void run(const render::RenderContextPointer& renderContext, - const Inputs& background); -}; - #endif // hifi_RenderForwardTask_h diff --git a/libraries/render-utils/src/Skinning.slh b/libraries/render-utils/src/Skinning.slh index 2d1f010029..a7edfb14a6 100644 --- a/libraries/render-utils/src/Skinning.slh +++ b/libraries/render-utils/src/Skinning.slh @@ -11,6 +11,10 @@ <@if not SKINNING_SLH@> <@def SKINNING_SLH@> +// Use dual quaternion skinning +// Must match #define SKIN_DQ in Model.h +<@def SKIN_DQ@> + const int MAX_CLUSTERS = 128; const int INDICES_PER_VERTEX = 4; @@ -18,6 +22,156 @@ layout(std140) uniform skinClusterBuffer { mat4 clusterMatrices[MAX_CLUSTERS]; }; +<@if SKIN_DQ@> + +mat4 dualQuatToMat4(vec4 real, vec4 dual) { + float twoRealXSq = 2.0 * real.x * real.x; + float twoRealYSq = 2.0 * real.y * real.y; + float twoRealZSq = 2.0 * real.z * real.z; + float twoRealXY = 2.0 * real.x * real.y; + float twoRealXZ = 2.0 * real.x * real.z; + float twoRealXW = 2.0 * real.x * real.w; + float twoRealZW = 2.0 * real.z * real.w; + float twoRealYZ = 2.0 * real.y * real.z; + float twoRealYW = 2.0 * real.y * real.w; + vec4 col0 = vec4(1.0 - twoRealYSq - twoRealZSq, + twoRealXY + twoRealZW, + twoRealXZ - twoRealYW, + 0.0); + vec4 col1 = vec4(twoRealXY - twoRealZW, + 1.0 - twoRealXSq - twoRealZSq, + twoRealYZ + twoRealXW, + 0.0); + vec4 col2 = vec4(twoRealXZ + twoRealYW, + twoRealYZ - twoRealXW, + 1.0 - twoRealXSq - twoRealYSq, + 0.0); + vec4 col3 = vec4(2.0 * (-dual.w * real.x + dual.x * real.w - dual.y * real.z + dual.z * real.y), + 2.0 * (-dual.w * real.y + dual.x * real.z + dual.y * real.w - dual.z * real.x), + 2.0 * (-dual.w * real.z - dual.x * real.y + dual.y * real.x + dual.z * real.w), + 1.0); + + return mat4(col0, col1, col2, col3); +} + +// dual quaternion linear blending +void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, out vec4 skinnedPosition) { + + // linearly blend scale and dual quaternion components + vec3 sAccum = vec3(0.0, 0.0, 0.0); + vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0); + vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0); + vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1]; + for (int i = 0; i < INDICES_PER_VERTEX; i++) { + mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])]; + float clusterWeight = skinClusterWeight[i]; + + vec3 scale = vec3(clusterMatrix[0]); + vec4 real = clusterMatrix[1]; + vec4 dual = clusterMatrix[2]; + + // to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity. + float dqClusterWeight = clusterWeight; + if (dot(real, polarityReference) < 0.0) { + dqClusterWeight = -clusterWeight; + } + + sAccum += scale * clusterWeight; + rAccum += real * dqClusterWeight; + dAccum += dual * dqClusterWeight; + } + + // normalize dual quaternion + float norm = length(rAccum); + rAccum /= norm; + dAccum /= norm; + + // conversion from dual quaternion to 4x4 matrix. + mat4 m = dualQuatToMat4(rAccum, dAccum); + skinnedPosition = m * (vec4(sAccum, 1) * inPosition); +} + +void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, vec3 inNormal, + out vec4 skinnedPosition, out vec3 skinnedNormal) { + + // linearly blend scale and dual quaternion components + vec3 sAccum = vec3(0.0, 0.0, 0.0); + vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0); + vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0); + vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1]; + + for (int i = 0; i < INDICES_PER_VERTEX; i++) { + mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])]; + float clusterWeight = skinClusterWeight[i]; + + vec3 scale = vec3(clusterMatrix[0]); + vec4 real = clusterMatrix[1]; + vec4 dual = clusterMatrix[2]; + + // to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity. + float dqClusterWeight = clusterWeight; + if (dot(real, polarityReference) < 0.0) { + dqClusterWeight = -clusterWeight; + } + + sAccum += scale * clusterWeight; + rAccum += real * dqClusterWeight; + dAccum += dual * dqClusterWeight; + } + + // normalize dual quaternion + float norm = length(rAccum); + rAccum /= norm; + dAccum /= norm; + + // conversion from dual quaternion to 4x4 matrix. + mat4 m = dualQuatToMat4(rAccum, dAccum); + skinnedPosition = m * (vec4(sAccum, 1) * inPosition); + skinnedNormal = vec3(m * vec4(inNormal, 0)); +} + +void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, vec3 inNormal, vec3 inTangent, + out vec4 skinnedPosition, out vec3 skinnedNormal, out vec3 skinnedTangent) { + + // linearly blend scale and dual quaternion components + vec3 sAccum = vec3(0.0, 0.0, 0.0); + vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0); + vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0); + vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1]; + + for (int i = 0; i < INDICES_PER_VERTEX; i++) { + mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])]; + float clusterWeight = skinClusterWeight[i]; + + vec3 scale = vec3(clusterMatrix[0]); + vec4 real = clusterMatrix[1]; + vec4 dual = clusterMatrix[2]; + + // to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity. + float dqClusterWeight = clusterWeight; + if (dot(real, polarityReference) < 0.0) { + dqClusterWeight = -clusterWeight; + } + + sAccum += scale * clusterWeight; + rAccum += real * dqClusterWeight; + dAccum += dual * dqClusterWeight; + } + + // normalize dual quaternion + float norm = length(rAccum); + rAccum /= norm; + dAccum /= norm; + + // conversion from dual quaternion to 4x4 matrix. + mat4 m = dualQuatToMat4(rAccum, dAccum); + skinnedPosition = m * (vec4(sAccum, 1) * inPosition); + skinnedNormal = vec3(m * vec4(inNormal, 0)); + skinnedTangent = vec3(m * vec4(inTangent, 0)); +} + +<@else@> // SKIN_DQ + void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, out vec4 skinnedPosition) { vec4 newPosition = vec4(0.0, 0.0, 0.0, 0.0); @@ -65,5 +219,6 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v skinnedTangent = newTangent.xyz; } +<@endif@> // if SKIN_DQ -<@endif@> \ No newline at end of file +<@endif@> // if not SKINNING_SLH diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index 63991f9422..0d0db7cbe3 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -52,13 +52,27 @@ void SoftAttachmentModel::updateClusterMatrices() { // TODO: cache these look-ups as an optimization int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); +#if defined(SKIN_DQ) glm::mat4 jointMatrix; if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); } else { jointMatrix = _rig.getJointTransform(cluster.jointIndex); } - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + + glm::mat4 m; + glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, m); + state.clusterTransforms[j] = Model::TransformDualQuaternion(m); +#else + glm::mat4 jointMatrix; + if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { + jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); + } else { + jointMatrix = _rig.getJointTransform(cluster.jointIndex); + } + + glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]); +#endif } } diff --git a/libraries/render-utils/src/forward_model.slf b/libraries/render-utils/src/forward_model.slf index 7b708a1d24..8931c605da 100644 --- a/libraries/render-utils/src/forward_model.slf +++ b/libraries/render-utils/src/forward_model.slf @@ -68,7 +68,7 @@ void main(void) { vec3 fragNormal; <$transformEyeToWorldDir(cam, _normal, fragNormal)$> - /* vec4 color = vec4(evalSkyboxGlobalColor( + vec4 color = vec4(evalSkyboxGlobalColor( cam._viewInverse, 1.0, 1.0, @@ -81,8 +81,6 @@ void main(void) { opacity); color.rgb += emissive * isEmissiveEnabled(); - */ - - _fragColor = vec4(albedo, opacity); - +// _fragColor = vec4(albedo, opacity); + _fragColor = color; } diff --git a/libraries/render-utils/src/forward_model_normal_map.slf b/libraries/render-utils/src/forward_model_normal_map.slf index a199483b9f..bc044d97ea 100644 --- a/libraries/render-utils/src/forward_model_normal_map.slf +++ b/libraries/render-utils/src/forward_model_normal_map.slf @@ -12,10 +12,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include ForwardBufferWrite.slh@> + !> + +<@include ForwardGlobalLight.slh@> +<$declareEvalSkyboxGlobalColor()$> <@include model/Material.slh@> +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + <@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, _SCRIBE_NULL, EMISSIVE, OCCLUSION, SCATTERING)$> @@ -26,6 +32,8 @@ in vec3 _normal; in vec3 _tangent; in vec3 _color; +out vec4 _fragColor; + void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); @@ -40,6 +48,15 @@ void main(void) { <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; albedo *= _color; + float metallic = getMaterialMetallic(mat); + vec3 fresnel = vec3(0.03); // Default Di-electric fresnel value + if (metallic <= 0.5) { + metallic = 0.0; + } else { + fresnel = albedo; + metallic = 1.0; + } + float roughness = getMaterialRoughness(mat); <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; @@ -52,13 +69,24 @@ void main(void) { float scattering = getMaterialScattering(mat); <$evalMaterialScattering(scatteringTex, scattering, matKey, scattering)$>; - packForwardFragment( - viewNormal, - opacity, + vec3 fragPosition = _position.xyz; + + TransformCamera cam = getTransformCamera(); + + vec4 color = vec4(evalSkyboxGlobalColor( + cam._viewInverse, + 1.0, + 1.0, + fragPosition, + viewNormal, albedo, - roughness, - getMaterialMetallic(mat), - emissive, - occlusionTex, - scattering); + fresnel, + metallic, + roughness), + opacity); + + color.rgb += emissive * isEmissiveEnabled(); + +// _fragColor = vec4(albedo, opacity); + _fragColor = color; } diff --git a/libraries/render-utils/src/forward_model_normal_specular_map.slf b/libraries/render-utils/src/forward_model_normal_specular_map.slf index ca6bbec3da..02a9242f72 100644 --- a/libraries/render-utils/src/forward_model_normal_specular_map.slf +++ b/libraries/render-utils/src/forward_model_normal_specular_map.slf @@ -12,10 +12,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include ForwardBufferWrite.slh@> + + !> + +<@include ForwardGlobalLight.slh@> +<$declareEvalSkyboxGlobalColor()$> <@include model/Material.slh@> +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + <@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC, EMISSIVE, OCCLUSION)$> @@ -26,6 +33,8 @@ in vec3 _normal; in vec3 _tangent; in vec3 _color; +out vec4 _fragColor; + void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); @@ -51,16 +60,32 @@ void main(void) { float metallic = getMaterialMetallic(mat); <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; + vec3 fresnel = vec3(0.03); // Default Di-electric fresnel value + if (metallic <= 0.5) { + metallic = 0.0; + } + else { + fresnel = albedo; + metallic = 1.0; + } + vec3 fragPosition = _position.xyz; - float scattering = getMaterialScattering(mat); + TransformCamera cam = getTransformCamera(); - packForwardFragment( - normalize(viewNormal.xyz), - opacity, + vec4 color = vec4(evalSkyboxGlobalColor( + cam._viewInverse, + 1.0, + 1.0, + fragPosition, + viewNormal, albedo, - roughness, + fresnel, metallic, - emissive, - occlusionTex, - scattering); + roughness), + opacity); + + color.rgb += emissive * isEmissiveEnabled(); + + // _fragColor = vec4(albedo, opacity); + _fragColor = color; } diff --git a/libraries/render-utils/src/forward_model_specular_map.slf b/libraries/render-utils/src/forward_model_specular_map.slf index d2fdd18794..6618c67ff1 100644 --- a/libraries/render-utils/src/forward_model_specular_map.slf +++ b/libraries/render-utils/src/forward_model_specular_map.slf @@ -12,10 +12,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include ForwardBufferWrite.slh@> + + !> + +<@include ForwardGlobalLight.slh@> +<$declareEvalSkyboxGlobalColor()$> <@include model/Material.slh@> +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + <@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, METALLIC, EMISSIVE, OCCLUSION)$> @@ -25,12 +32,13 @@ in vec2 _texCoord1; in vec3 _normal; in vec3 _color; +out vec4 _fragColor; void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex)$> - <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> + !> float opacity = 1.0; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; @@ -48,7 +56,14 @@ void main(void) { float metallic = getMaterialMetallic(mat); <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; - + vec3 fresnel = vec3(0.03); // Default Di-electric fresnel value + if (metallic <= 0.5) { + metallic = 0.0; + } + else { + fresnel = albedo; + metallic = 1.0; + }/* float scattering = getMaterialScattering(mat); packForwardFragment( @@ -60,4 +75,26 @@ void main(void) { emissive, occlusionTex, scattering); + */ + vec3 fragPosition = _position.xyz; + + TransformCamera cam = getTransformCamera(); + vec3 fragNormal; + <$transformEyeToWorldDir(cam, _normal, fragNormal)$> + + vec4 color = vec4(evalSkyboxGlobalColor( + cam._viewInverse, + 1.0, + 1.0, + fragPosition, + fragNormal, + albedo, + fresnel, + metallic, + roughness), + opacity); + color.rgb += emissive * isEmissiveEnabled(); + + // _fragColor = vec4(albedo, opacity); + _fragColor = color; } diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 4254280fa1..b010657049 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -131,7 +131,7 @@ const ShapePipelinePointer ShapePlumber::pickPipeline(RenderArgs* args, const Ke auto factoryIt = ShapePipeline::_globalCustomFactoryMap.find(key.getCustom()); if ((factoryIt != ShapePipeline::_globalCustomFactoryMap.end()) && (factoryIt)->second) { // found a factory for the custom key, can now generate a shape pipeline for this case: - addPipelineHelper(Filter(key), key, 0, (factoryIt)->second(*this, key)); + addPipelineHelper(Filter(key), key, 0, (factoryIt)->second(*this, key, *(args->_batch))); return pickPipeline(args, key); } else { diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index be77e2f95e..9e05cfb81b 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -301,7 +301,7 @@ protected: ItemSetter _itemSetter; public: using CustomKey = uint8_t; - using CustomFactory = std::function (const ShapePlumber& plumber, const ShapeKey& key)>; + using CustomFactory = std::function (const ShapePlumber& plumber, const ShapeKey& key, gpu::Batch& batch)>; using CustomFactoryMap = std::map; static CustomFactoryMap _globalCustomFactoryMap; diff --git a/libraries/shared/src/DualQuaternion.cpp b/libraries/shared/src/DualQuaternion.cpp new file mode 100644 index 0000000000..2accbed2a9 --- /dev/null +++ b/libraries/shared/src/DualQuaternion.cpp @@ -0,0 +1,92 @@ +// +// DualQuaternion.cpp +// +// Created by Anthony J. Thibault on Dec 13th 2017. +// Copyright (c) 2017 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "DualQuaternion.h" +#include "GLMHelpers.h" + +// delegating constructor +DualQuaternion::DualQuaternion() : _real(1.0f, 0.0f, 0.0f, 0.0), _dual(0.0f, 0.0f, 0.0f, 0.0f) { +} + +DualQuaternion::DualQuaternion(const glm::mat4& m) : DualQuaternion(glmExtractRotation(m), extractTranslation(m)) { +} + +DualQuaternion::DualQuaternion(const glm::quat& real, const glm::quat& dual) : _real(real), _dual(dual) { +} + +DualQuaternion::DualQuaternion(const glm::vec4& real, const glm::vec4& dual) : + _real(real.w, real.x, real.y, real.z), + _dual(dual.w, dual.x, dual.y, dual.z) { +} + +DualQuaternion::DualQuaternion(const glm::quat& rotation, const glm::vec3& translation) { + _real = rotation; + _dual = glm::quat(0.0f, 0.5f * translation.x, 0.5f * translation.y, 0.5f * translation.z) * rotation; +} + +DualQuaternion DualQuaternion::operator*(const DualQuaternion& rhs) const { + return DualQuaternion(_real * rhs._real, _real * rhs._dual + _dual * rhs._real); +} + +DualQuaternion DualQuaternion::operator*(float scalar) const { + return DualQuaternion(_real * scalar, _dual * scalar); +} + +DualQuaternion DualQuaternion::operator+(const DualQuaternion& rhs) const { + return DualQuaternion(_real + rhs._real, _dual + rhs._dual); +} + +glm::vec3 DualQuaternion::xformPoint(const glm::vec3& rhs) const { + DualQuaternion v(glm::quat(), glm::quat(0.0f, rhs.x, rhs.y, rhs.z)); + DualQuaternion dualConj(glm::conjugate(_real), -glm::conjugate(_dual)); + DualQuaternion result = *this * v * dualConj; + return vec3(result._dual.x, result._dual.y, result._dual.z); +} + +glm::quat DualQuaternion::getRotation() const { + return _real; +} + +glm::vec3 DualQuaternion::getTranslation() const { + glm::quat result = 2.0f * (_dual * glm::inverse(_real)); + return glm::vec3(result.x, result.y, result.z); +} + +glm::vec3 DualQuaternion::xformVector(const glm::vec3& rhs) const { + return _real * rhs; +} + +DualQuaternion DualQuaternion::inverse() const { + glm::quat invReal = glm::inverse(_real); + return DualQuaternion(invReal, - invReal * _dual * invReal); +} + +DualQuaternion DualQuaternion::conjugate() const { + return DualQuaternion(glm::conjugate(_real), glm::conjugate(_dual)); +} + +float DualQuaternion::length() const { + float dot = this->dot(*this); + return sqrtf(dot); +} + +DualQuaternion DualQuaternion::normalize() const { + float invLen = 1.0f / length(); + return *this * invLen; +} + +float DualQuaternion::dot(const DualQuaternion& rhs) const { + DualQuaternion result = *this * conjugate(); + return result._real.w; +} + +DualQuaternion DualQuaternion::operator-() const { + return DualQuaternion(-_real, -_dual); +} diff --git a/libraries/shared/src/DualQuaternion.h b/libraries/shared/src/DualQuaternion.h new file mode 100644 index 0000000000..709c089fdc --- /dev/null +++ b/libraries/shared/src/DualQuaternion.h @@ -0,0 +1,62 @@ +// +// DualQuaternion.h +// +// Created by Anthony J. Thibault on Dec 13th 2017. +// Copyright (c) 2017 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#ifndef hifi_DualQuaternion +#define hifi_DualQuaternion + +#include +#include +#include +#include + +class DualQuaternion { +public: + DualQuaternion(); + explicit DualQuaternion(const glm::mat4& m); + DualQuaternion(const glm::quat& real, const glm::quat& imag); + DualQuaternion(const glm::quat& rotation, const glm::vec3& translation); + DualQuaternion(const glm::vec4& real, const glm::vec4& imag); + DualQuaternion operator*(const DualQuaternion& rhs) const; + DualQuaternion operator*(float scalar) const; + DualQuaternion operator+(const DualQuaternion& rhs) const; + + const glm::quat& real() const { return _real; } + glm::quat& real() { return _real; } + + const glm::quat& dual() const { return _dual; } + glm::quat& dual() { return _dual; } + + glm::quat getRotation() const; + glm::vec3 getTranslation() const; + + glm::vec3 xformPoint(const glm::vec3& rhs) const; + glm::vec3 xformVector(const glm::vec3& rhs) const; + + DualQuaternion inverse() const; + DualQuaternion conjugate() const; + float length() const; + DualQuaternion normalize() const; + float dot(const DualQuaternion& rhs) const; + DualQuaternion operator-() const; + +protected: + friend QDebug operator<<(QDebug debug, const DualQuaternion& pose); + glm::quat _real; + glm::quat _dual; +}; + + +inline QDebug operator<<(QDebug debug, const DualQuaternion& dq) { + debug << "AnimPose, real = (" << dq._real.x << dq._real.y << dq._real.z << dq._real.w << "), dual = (" << dq._dual.x << dq._dual.y << dq._dual.z << dq._dual.w << ")"; + return debug; +} + +#endif diff --git a/libraries/shared/src/Transform.h b/libraries/shared/src/Transform.h index 7a39314f4d..90bfc1aaa6 100644 --- a/libraries/shared/src/Transform.h +++ b/libraries/shared/src/Transform.h @@ -58,7 +58,7 @@ public: _rotation(rotation), _scale(scale), _translation(translation), - _flags(FLAG_CACHE_INVALID_BITSET) // invalid cache + _flags(0xf) // FLAG_TRANSLATION | FLAG_ROTATION | FLAG_SCALING | FLAG_NON_UNIFORM { if (!isValidScale(_scale)) { _scale = Vec3(1.0f); diff --git a/tests/shared/src/DualQuaternionTests.cpp b/tests/shared/src/DualQuaternionTests.cpp new file mode 100644 index 0000000000..fe14d9d166 --- /dev/null +++ b/tests/shared/src/DualQuaternionTests.cpp @@ -0,0 +1,115 @@ +// +// DualQuaternionTests.cpp +// tests/shared/src +// +// 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 +// + +#include + +#include "DualQuaternionTests.h" + +#include +#include +#include +#include + +#include <../GLMTestUtils.h> +#include <../QTestExtensions.h> + +QTEST_MAIN(DualQuaternionTests) + +static void quatComp(const glm::quat& q1, const glm::quat& q2) { + QCOMPARE_WITH_ABS_ERROR(q1.x, q2.x, EPSILON); + QCOMPARE_WITH_ABS_ERROR(q1.y, q2.y, EPSILON); + QCOMPARE_WITH_ABS_ERROR(q1.z, q2.z, EPSILON); + QCOMPARE_WITH_ABS_ERROR(q1.w, q2.w, EPSILON); +} + +void DualQuaternionTests::ctor() { + glm::quat real = angleAxis(PI / 2.0f, Vectors::UNIT_Y); + glm::quat dual(0.0f, 1.0f, 2.0f, 3.0f); + + DualQuaternion dq(real, dual); + quatComp(real, dq.real()); + quatComp(dual, dq.dual()); + + glm::quat rotation = angleAxis(PI / 3.0f, Vectors::UNIT_X); + glm::vec3 translation(1.0, 2.0f, 3.0f); + dq = DualQuaternion(rotation, translation); + quatComp(rotation, dq.getRotation()); + QCOMPARE_WITH_ABS_ERROR(translation, dq.getTranslation(), EPSILON); + + rotation = angleAxis(-2.0f * PI / 7.0f, Vectors::UNIT_Z); + translation = glm::vec3(-1.0, 12.0f, 2.0f); + glm::mat4 m = createMatFromQuatAndPos(rotation, translation); + dq = DualQuaternion(m); + quatComp(rotation, dq.getRotation()); + QCOMPARE_WITH_ABS_ERROR(translation, dq.getTranslation(), EPSILON); +} + +void DualQuaternionTests::mult() { + + glm::quat rotation = angleAxis(PI / 3.0f, Vectors::UNIT_X); + glm::vec3 translation(1.0, 2.0f, 3.0f); + glm::mat4 m1 = createMatFromQuatAndPos(rotation, translation); + DualQuaternion dq1(m1); + + rotation = angleAxis(-2.0f * PI / 7.0f, Vectors::UNIT_Z); + translation = glm::vec3(-1.0, 12.0f, 2.0f); + glm::mat4 m2 = createMatFromQuatAndPos(rotation, translation); + DualQuaternion dq2(m2); + + DualQuaternion dq3 = dq1 * dq2; + glm::mat4 m3 = m1 * m2; + + rotation = glmExtractRotation(m3); + translation = extractTranslation(m3); + + quatComp(rotation, dq3.getRotation()); + QCOMPARE_WITH_ABS_ERROR(translation, dq3.getTranslation(), EPSILON); +} + +void DualQuaternionTests::xform() { + + glm::quat rotation = angleAxis(PI / 3.0f, Vectors::UNIT_X); + glm::vec3 translation(1.0, 2.0f, 3.0f); + glm::mat4 m1 = createMatFromQuatAndPos(rotation, translation); + DualQuaternion dq1(m1); + + rotation = angleAxis(-2.0f * PI / 7.0f, Vectors::UNIT_Z); + translation = glm::vec3(-1.0, 12.0f, 2.0f); + glm::mat4 m2 = createMatFromQuatAndPos(rotation, translation); + DualQuaternion dq2(m2); + + DualQuaternion dq3 = dq1 * dq2; + glm::mat4 m3 = m1 * m2; + + glm::vec3 p(1.0f, 2.0f, 3.0f); + + glm::vec3 p1 = transformPoint(m3, p); + glm::vec3 p2 = dq3.xformPoint(p); + + QCOMPARE_WITH_ABS_ERROR(p1, p2, 0.001f); + + p1 = transformVectorFast(m3, p); + p2 = dq3.xformVector(p); + + QCOMPARE_WITH_ABS_ERROR(p1, p2, 0.001f); +} + +void DualQuaternionTests::trans() { + glm::vec3 t1 = glm::vec3(); + DualQuaternion dq1(Quaternions::IDENTITY, t1); + glm::vec3 t2 = glm::vec3(1.0f, 2.0f, 3.0f); + DualQuaternion dq2(angleAxis(PI / 3.0f, Vectors::UNIT_X), t2); + glm::vec3 t3 = glm::vec3(3.0f, 2.0f, 1.0f); + DualQuaternion dq3(angleAxis(PI / 5.0f, Vectors::UNIT_Y), t3); + + QCOMPARE_WITH_ABS_ERROR(t1, dq1.getTranslation(), 0.001f); + QCOMPARE_WITH_ABS_ERROR(t2, dq2.getTranslation(), 0.001f); + QCOMPARE_WITH_ABS_ERROR(t3, dq3.getTranslation(), 0.001f); +} diff --git a/tests/shared/src/DualQuaternionTests.h b/tests/shared/src/DualQuaternionTests.h new file mode 100644 index 0000000000..aa4b40cfd6 --- /dev/null +++ b/tests/shared/src/DualQuaternionTests.h @@ -0,0 +1,25 @@ +// +// DualQuaternionTests.h +// tests/shared/src +// +// 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 +// + +#ifndef hifi_DualQuaternionTests_h +#define hifi_DualQuaternionTests_h + +#include + +class DualQuaternionTests : public QObject { + Q_OBJECT +private slots: + void ctor(); + void mult(); + void xform(); + void trans(); +}; + +#endif // hifi_DualQuaternionTests_h