diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt
index d7e4b1ae7c..71341f3f11 100644
--- a/interface/CMakeLists.txt
+++ b/interface/CMakeLists.txt
@@ -191,7 +191,7 @@ endif()
# link required hifi libraries
link_hifi_libraries(
shared octree ktx gpu gl gpu-gl procedural model render
- recording fbx networking model-networking entities avatars
+ recording fbx networking model-networking entities avatars trackers
audio audio-client animation script-engine physics
render-utils entities-renderer avatars-renderer ui auto-updater
controllers plugins image trackers
diff --git a/interface/resources/images/Announce-Blast.svg b/interface/resources/images/Announce-Blast.svg
new file mode 100644
index 0000000000..56cdb10b9f
--- /dev/null
+++ b/interface/resources/images/Announce-Blast.svg
@@ -0,0 +1,21 @@
+
+
+
diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml
index 04e784e2ba..d288872289 100644
--- a/interface/resources/qml/controls/TabletWebView.qml
+++ b/interface/resources/qml/controls/TabletWebView.qml
@@ -16,10 +16,9 @@ Item {
property var parentStackItem: null
property int headerHeight: 70
property string url
- property alias address: displayUrl.text //for compatibility
property string scriptURL
property alias eventBridge: eventBridgeWrapper.eventBridge
- property bool keyboardEnabled: HMD.active
+ property bool keyboardEnabled: false
property bool keyboardRaised: false
property bool punctuationMode: false
property bool isDesktop: false
@@ -82,6 +81,7 @@ Item {
color: hifi.colors.baseGray
font.pixelSize: 12
verticalAlignment: Text.AlignLeft
+ text: webview.url
anchors {
top: nav.bottom
horizontalCenter: parent.horizontalCenter;
@@ -159,7 +159,6 @@ Item {
function loadUrl(url) {
webview.url = url
web.url = webview.url;
- web.address = webview.url;
}
function onInitialPage(url) {
@@ -239,7 +238,7 @@ Item {
worldId: WebEngineScript.MainWorld
}
- property string urlTag: "noDownload=false";
+ property string urlTag: "noDownload=false";
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
property string newUrl: ""
@@ -253,7 +252,6 @@ Item {
});
webview.profile.httpUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36";
- web.address = url;
}
onFeaturePermissionRequested: {
@@ -266,7 +264,7 @@ Item {
keyboard.resetShiftMode(false);
// Required to support clicking on "hifi://" links
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
- var url = loadRequest.url.toString();
+ var url = loadRequest.url.toString();
if (urlHandler.canHandleUrl(url)) {
if (urlHandler.handleUrl(url)) {
root.stop();
@@ -275,15 +273,15 @@ Item {
}
if (WebEngineView.LoadFailedStatus == loadRequest.status) {
- console.log(" Tablet WebEngineView failed to laod url: " + loadRequest.url.toString());
+ console.log(" Tablet WebEngineView failed to load url: " + loadRequest.url.toString());
}
if (WebEngineView.LoadSucceededStatus == loadRequest.status) {
- web.address = webview.url;
if (startingUp) {
web.initialPage = webview.url;
startingUp = false;
}
+ webview.forceActiveFocus();
}
}
@@ -297,6 +295,7 @@ Item {
HiFiControls.Keyboard {
id: keyboard
raised: parent.keyboardEnabled && parent.keyboardRaised
+ numeric: parent.punctuationMode
anchors {
left: parent.left
@@ -307,7 +306,7 @@ Item {
Component.onCompleted: {
web.isDesktop = (typeof desktop !== "undefined");
- address = url;
+ keyboardEnabled = HMD.active;
}
Keys.onPressed: {
diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml
index 9617b41150..fd76c03e45 100644
--- a/interface/resources/qml/hifi/Card.qml
+++ b/interface/resources/qml/hifi/Card.qml
@@ -167,11 +167,9 @@ Item {
Rectangle {
id: lozenge;
visible: isAnnouncement;
- color: hifi.colors.redHighlight;
+ color: lozengeHot.containsMouse ? hifi.colors.redAccent : hifi.colors.redHighlight;
anchors.fill: infoRow;
radius: lozenge.height / 2.0;
- border.width: lozengeHot.containsMouse ? 4 : 0;
- border.color: "white";
}
Row {
id: infoRow;
diff --git a/interface/resources/qml/hifi/ComboDialog.qml b/interface/resources/qml/hifi/ComboDialog.qml
index e328805d74..83fcad18c7 100644
--- a/interface/resources/qml/hifi/ComboDialog.qml
+++ b/interface/resources/qml/hifi/ComboDialog.qml
@@ -23,7 +23,8 @@ Item {
property var callbackFunction;
property int dialogWidth;
property int dialogHeight;
- property int comboOptionTextSize: 18;
+ property int comboOptionTextSize: 16;
+ property int comboBodyTextSize: 16;
FontLoader { id: ralewayRegular; source: "../../fonts/Raleway-Regular.ttf"; }
FontLoader { id: ralewaySemiBold; source: "../../fonts/Raleway-SemiBold.ttf"; }
visible: false;
@@ -63,7 +64,7 @@ Item {
anchors.left: parent.left;
anchors.leftMargin: 20;
size: 24;
- color: 'black';
+ color: hifi.colors.darkGray;
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignTop;
}
@@ -141,6 +142,7 @@ Item {
height: 30;
size: comboOptionTextSize;
wrapMode: Text.WordWrap;
+ color: hifi.colors.darkGray;
}
RalewayRegular {
@@ -148,11 +150,12 @@ Item {
text: bodyText;
anchors.top: optionTitle.bottom;
anchors.left: comboOptionSelected.right;
- anchors.leftMargin: 25;
+ anchors.leftMargin: 10;
anchors.right: parent.right;
anchors.rightMargin: 10;
- size: comboOptionTextSize;
+ size: comboBodyTextSize;
wrapMode: Text.WordWrap;
+ color: hifi.colors.darkGray;
}
MouseArea {
diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml
index fd3472b7be..fc108f47e3 100644
--- a/interface/resources/qml/hifi/Feed.qml
+++ b/interface/resources/qml/hifi/Feed.qml
@@ -156,10 +156,8 @@ Column {
function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches
var words = filter.toUpperCase().split(/\s+/).filter(identity);
function suggestable(story) {
- if (story.action === 'snapshot') {
- return true;
- }
- return story.place_name !== AddressManager.placename; // Not our entry, but do show other entry points to current domain.
+ // We could filter out places we don't want to suggest, such as those where (story.place_name === AddressManager.placename) or (story.username === Account.username).
+ return true;
}
function matches(story) {
if (!words.length) {
diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml
index 833e641b09..1755d2fbec 100644
--- a/interface/resources/qml/hifi/Pal.qml
+++ b/interface/resources/qml/hifi/Pal.qml
@@ -44,6 +44,7 @@ Rectangle {
property var activeTab: "nearbyTab";
property bool currentlyEditingDisplayName: false
property bool punctuationMode: false;
+ property var eventBridge;
HifiConstants { id: hifi; }
@@ -1012,10 +1013,10 @@ Rectangle {
onClicked: {
popupComboDialog("Set your availability:",
availabilityComboBox.availabilityStrings,
- ["Your username will be visible in everyone's 'Nearby' list.\nAnyone will be able to jump to your location from within the 'Nearby' list.",
- "Your location will be visible in the 'Connections' list only for those with whom you are connected or friends.\nThey will be able to jump to your location if the domain allows.",
- "Your location will be visible in the 'Connections' list only for those with whom you are friends.\nThey will be able to jump to your location if the domain allows.",
- "Your location will not be visible in the 'Connections' list of any other users. Only domain admins will be able to see your username in the 'Nearby' list."],
+ ["Your username will be visible in everyone's 'Nearby' list. Anyone will be able to jump to your location from within the 'Nearby' list.",
+ "Your location will be visible in the 'Connections' list only for those with whom you are connected or friends. They'll be able to jump to your location if the domain allows.",
+ "Your location will be visible in the 'Connections' list only for those with whom you are friends. They'll be able to jump to your location if the domain allows. You will only receive 'Happening Now' notifications in 'Go To' from friends.",
+ "You will appear offline in the 'Connections' list, and you will not receive 'Happening Now' notifications in 'Go To'."],
["all", "connections", "friends", "none"]);
}
onEntered: availabilityComboBox.color = hifi.colors.lightGrayText;
@@ -1036,139 +1037,16 @@ Rectangle {
}
} // Keyboard
- Item {
- id: webViewContainer;
- anchors.fill: parent;
-
- Rectangle {
- id: navigationContainer;
- visible: userInfoViewer.visible;
- height: 60;
- anchors {
- top: parent.top;
- left: parent.left;
- right: parent.right;
- }
- color: hifi.colors.faintGray;
-
- Item {
- id: backButton
- anchors {
- top: parent.top;
- left: parent.left;
- }
- height: parent.height - addressBar.height;
- width: parent.width/2;
-
- FiraSansSemiBold {
- // Properties
- text: "BACK";
- elide: Text.ElideRight;
- // Anchors
- anchors.fill: parent;
- // Text Size
- size: 16;
- // Text Positioning
- verticalAlignment: Text.AlignVCenter
- horizontalAlignment: Text.AlignHCenter;
- // Style
- color: backButtonMouseArea.containsMouse || !userInfoViewer.canGoBack ? hifi.colors.lightGray : hifi.colors.darkGray;
- MouseArea {
- id: backButtonMouseArea;
- anchors.fill: parent
- hoverEnabled: enabled
- onClicked: {
- if (userInfoViewer.canGoBack) {
- userInfoViewer.goBack();
- }
- }
- }
- }
- }
-
- Item {
- id: closeButtonContainer
- anchors {
- top: parent.top;
- right: parent.right;
- }
- height: parent.height - addressBar.height;
- width: parent.width/2;
-
- FiraSansSemiBold {
- id: closeButton;
- // Properties
- text: "CLOSE";
- elide: Text.ElideRight;
- // Anchors
- anchors.fill: parent;
- // Text Size
- size: 16;
- // Text Positioning
- verticalAlignment: Text.AlignVCenter
- horizontalAlignment: Text.AlignHCenter;
- // Style
- color: hifi.colors.redHighlight;
- MouseArea {
- anchors.fill: parent
- hoverEnabled: enabled
- onClicked: userInfoViewer.visible = false;
- onEntered: closeButton.color = hifi.colors.redAccent;
- onExited: closeButton.color = hifi.colors.redHighlight;
- }
- }
- }
-
- Item {
- id: addressBar
- anchors {
- top: closeButtonContainer.bottom;
- left: parent.left;
- right: parent.right;
- }
- height: 30;
- width: parent.width;
-
- FiraSansRegular {
- // Properties
- text: userInfoViewer.url;
- elide: Text.ElideRight;
- // Anchors
- anchors.fill: parent;
- anchors.leftMargin: 5;
- // Text Size
- size: 14;
- // Text Positioning
- verticalAlignment: Text.AlignVCenter
- horizontalAlignment: Text.AlignLeft;
- // Style
- color: hifi.colors.lightGray;
- }
- }
- }
-
- Rectangle {
- id: webViewBackground;
- color: "white";
- visible: userInfoViewer.visible;
- anchors {
- top: navigationContainer.bottom;
- bottom: parent.bottom;
- left: parent.left;
- right: parent.right;
- }
- }
-
- HifiControls.WebView {
- id: userInfoViewer;
- anchors {
- top: navigationContainer.bottom;
- bottom: parent.bottom;
- left: parent.left;
- right: parent.right;
- }
- visible: false;
+ HifiControls.TabletWebView {
+ eventBridge: pal.eventBridge;
+ id: userInfoViewer;
+ anchors {
+ top: parent.top;
+ bottom: parent.bottom;
+ left: parent.left;
+ right: parent.right;
}
+ visible: false;
}
// Timer used when selecting nearbyTable rows that aren't yet present in the model
diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml
index d95dbc2e55..0514bfbffd 100644
--- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml
+++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml
@@ -34,9 +34,6 @@ ScrollingWindow {
property var runningScriptsModel: ListModel { }
property bool isHMD: false
- onVisibleChanged: console.log("Running scripts visible changed to " + visible)
- onShownChanged: console.log("Running scripts visible changed to " + visible)
-
Settings {
category: "Overlay.RunningScripts"
property alias x: root.x
diff --git a/interface/resources/qml/hifi/tablet/Tablet.qml b/interface/resources/qml/hifi/tablet/Tablet.qml
index 8ad6339d88..18f88b7718 100644
--- a/interface/resources/qml/hifi/tablet/Tablet.qml
+++ b/interface/resources/qml/hifi/tablet/Tablet.qml
@@ -65,7 +65,11 @@ Item {
});
// pass a reference to the tabletRoot object to the button.
- button.tabletRoot = parent.parent;
+ if (tabletRoot) {
+ button.tabletRoot = tabletRoot;
+ } else {
+ button.tabletRoot = parent.parent;
+ }
sortButtons();
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 64b18ec522..e882bad7e1 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -129,12 +129,12 @@
#include
#include
#include
-
+#include
#include "AudioClient.h"
#include "audio/AudioScope.h"
#include "avatar/AvatarManager.h"
-#include "avatar/ScriptAvatar.h"
+#include "avatar/MyHead.h"
#include "CrashHandler.h"
#include "devices/DdeFaceTracker.h"
#include "devices/Leapmotion.h"
@@ -1586,6 +1586,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(&domainHandler, &DomainHandler::hostnameChanged, this, &Application::addAssetToWorldMessageClose);
updateSystemTabletMode();
+
+ connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged);
}
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
@@ -2191,7 +2193,7 @@ void Application::paintGL() {
_myCamera.setOrientation(glm::quat_cast(camMat));
} else {
_myCamera.setPosition(myAvatar->getDefaultEyePosition());
- _myCamera.setOrientation(myAvatar->getHead()->getCameraOrientation());
+ _myCamera.setOrientation(myAvatar->getMyHead()->getCameraOrientation());
}
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
if (isHMDMode()) {
@@ -4087,6 +4089,30 @@ void Application::cycleCamera() {
cameraMenuChanged(); // handle the menu change
}
+void Application::cameraModeChanged() {
+ switch (_myCamera.getMode()) {
+ case CAMERA_MODE_FIRST_PERSON:
+ Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, true);
+ break;
+ case CAMERA_MODE_THIRD_PERSON:
+ Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true);
+ break;
+ case CAMERA_MODE_MIRROR:
+ Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, true);
+ break;
+ case CAMERA_MODE_INDEPENDENT:
+ Menu::getInstance()->setIsOptionChecked(MenuOption::IndependentMode, true);
+ break;
+ case CAMERA_MODE_ENTITY:
+ Menu::getInstance()->setIsOptionChecked(MenuOption::CameraEntityMode, true);
+ break;
+ default:
+ break;
+ }
+ cameraMenuChanged();
+}
+
+
void Application::cameraMenuChanged() {
if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
if (_myCamera.getMode() != CAMERA_MODE_MIRROR) {
@@ -5435,7 +5461,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance());
}
- scriptEngine->registerGlobalObject("Overlays", &_overlays);
scriptEngine->registerGlobalObject("Rates", new RatesScriptingInterface(this));
// hook our avatar and avatar hash map object into this script engine
@@ -5534,6 +5559,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
auto entityScriptServerLog = DependencyManager::get();
scriptEngine->registerGlobalObject("EntityScriptServerLog", entityScriptServerLog.data());
+ scriptEngine->registerGlobalObject("AvatarInputs", AvatarInputs::getInstance());
+
qScriptRegisterMetaType(scriptEngine, OverlayIDtoScriptValue, OverlayIDfromScriptValue);
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 367d878dd4..5027c58349 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -373,6 +373,7 @@ public slots:
static void showHelp();
void cycleCamera();
+ void cameraModeChanged();
void cameraMenuChanged();
void toggleOverlays();
void setOverlaysVisible(bool visible);
diff --git a/interface/src/FancyCamera.cpp b/interface/src/FancyCamera.cpp
index 7bb64a4a8e..298cab9948 100644
--- a/interface/src/FancyCamera.cpp
+++ b/interface/src/FancyCamera.cpp
@@ -12,6 +12,9 @@
#include "Application.h"
+PickRay FancyCamera::computePickRay(float x, float y) const {
+ return qApp->computePickRay(x, y);
+}
QUuid FancyCamera::getCameraEntity() const {
if (_cameraEntity != nullptr) {
diff --git a/interface/src/FancyCamera.h b/interface/src/FancyCamera.h
index 66f7a07dbd..3552dc6ca8 100644
--- a/interface/src/FancyCamera.h
+++ b/interface/src/FancyCamera.h
@@ -11,7 +11,7 @@
#ifndef hifi_FancyCamera_h
#define hifi_FancyCamera_h
-#include "Camera.h"
+#include
#include
@@ -30,6 +30,8 @@ public:
FancyCamera() : Camera() {}
EntityItemPointer getCameraEntityPointer() const { return _cameraEntity; }
+ PickRay computePickRay(float x, float y) const override;
+
public slots:
QUuid getCameraEntity() const;
diff --git a/interface/src/InterfaceDynamicFactory.cpp b/interface/src/InterfaceDynamicFactory.cpp
index 5951ccef9e..5acc0700c0 100644
--- a/interface/src/InterfaceDynamicFactory.cpp
+++ b/interface/src/InterfaceDynamicFactory.cpp
@@ -17,6 +17,9 @@
#include
#include
#include
+#include
+#include
+#include
#include
#include "InterfaceDynamicFactory.h"
@@ -38,9 +41,15 @@ EntityDynamicPointer interfaceDynamicFactory(EntityDynamicType type, const QUuid
return std::make_shared(id, ownerEntity);
case DYNAMIC_TYPE_FAR_GRAB:
return std::make_shared(id, ownerEntity);
+ case DYNAMIC_TYPE_SLIDER:
+ return std::make_shared(id, ownerEntity);
+ case DYNAMIC_TYPE_BALL_SOCKET:
+ return std::make_shared(id, ownerEntity);
+ case DYNAMIC_TYPE_CONE_TWIST:
+ return std::make_shared(id, ownerEntity);
}
- Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity dynamic type");
+ qDebug() << "Unknown entity dynamic type";
return EntityDynamicPointer();
}
diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp
index 7bf48d98d2..78a503bc71 100644
--- a/interface/src/Util.cpp
+++ b/interface/src/Util.cpp
@@ -142,11 +142,6 @@ void renderWorldBox(gpu::Batch& batch) {
geometryCache->renderSolidSphereInstance(batch, GREY);
}
-// Return a random vector of average length 1
-const glm::vec3 randVector() {
- return glm::vec3(randFloat() - 0.5f, randFloat() - 0.5f, randFloat() - 0.5f) * 2.0f;
-}
-
// Do some basic timing tests and report the results
void runTimingTests() {
// How long does it take to make a call to get the time?
diff --git a/interface/src/Util.h b/interface/src/Util.h
index 60e38ae0ec..b1b4c78bcb 100644
--- a/interface/src/Util.h
+++ b/interface/src/Util.h
@@ -17,9 +17,6 @@
#include
-float randFloat();
-const glm::vec3 randVector();
-
void renderWorldBox(gpu::Batch& batch);
void runTimingTests();
diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp
index 98ff687eb3..627dc2ba02 100644
--- a/interface/src/avatar/AvatarActionHold.cpp
+++ b/interface/src/avatar/AvatarActionHold.cpp
@@ -113,7 +113,8 @@ void AvatarActionHold::prepareForPhysicsSimulation() {
}
bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position,
- glm::vec3& linearVelocity, glm::vec3& angularVelocity) {
+ glm::vec3& linearVelocity, glm::vec3& angularVelocity,
+ float& linearTimeScale, float& angularTimeScale) {
auto avatarManager = DependencyManager::get();
auto holdingAvatar = std::static_pointer_cast(avatarManager->getAvatarBySessionID(_holderID));
@@ -213,6 +214,9 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::
// update linearVelocity based on offset via _relativePosition;
linearVelocity = linearVelocity + glm::cross(angularVelocity, position - palmPosition);
+
+ linearTimeScale = _linearTimeScale;
+ angularTimeScale = _angularTimeScale;
});
return true;
diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h
index f0b42111ed..7eeda53e06 100644
--- a/interface/src/avatar/AvatarActionHold.h
+++ b/interface/src/avatar/AvatarActionHold.h
@@ -38,7 +38,8 @@ public:
bool getAvatarRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
virtual bool getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position,
- glm::vec3& linearVelocity, glm::vec3& angularVelocity) override;
+ glm::vec3& linearVelocity, glm::vec3& angularVelocity,
+ float& linearTimeScale, float& angularTimeScale) override;
virtual void prepareForPhysicsSimulation() override;
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index 04ab1531ba..1306ce03ea 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -32,9 +32,9 @@
#include
#include
#include
+#include
#include "Application.h"
-#include "Avatar.h"
#include "AvatarManager.h"
#include "InterfaceLogging.h"
#include "Menu.h"
@@ -299,7 +299,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
}
AvatarSharedPointer AvatarManager::newSharedAvatar() {
- return std::make_shared(qApp->thread(), std::make_shared());
+ return std::make_shared(qApp->thread(), std::make_shared());
}
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h
index c67088a4be..9df1639853 100644
--- a/interface/src/avatar/AvatarManager.h
+++ b/interface/src/avatar/AvatarManager.h
@@ -21,13 +21,11 @@
#include
#include
#include
+#include
+#include
-#include "Avatar.h"
#include "MyAvatar.h"
-#include "AvatarMotionState.h"
-#include "ScriptAvatar.h"
-class MyAvatar;
class AudioInjector;
class AvatarManager : public AvatarHashMap {
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 5e285f21ba..3f3ce7d9e9 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "MyAvatar.h"
+
#include
#include
@@ -43,11 +45,12 @@
#include
#include
+#include "MyHead.h"
+#include "MySkeletonModel.h"
#include "Application.h"
#include "AvatarManager.h"
#include "AvatarActionHold.h"
#include "Menu.h"
-#include "MyAvatar.h"
#include "Util.h"
#include "InterfaceLogging.h"
#include "DebugDraw.h"
@@ -96,23 +99,12 @@ static const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { -0.4016716778278351f, 0.91
MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
Avatar(thread, rig),
- _wasPushing(false),
- _isPushing(false),
- _isBeingPushed(false),
- _isBraking(false),
- _isAway(false),
- _boomLength(ZOOM_DEFAULT),
_yawSpeed(YAW_SPEED_DEFAULT),
_pitchSpeed(PITCH_SPEED_DEFAULT),
- _thrust(0.0f),
- _actionMotorVelocity(0.0f),
- _scriptedMotorVelocity(0.0f),
_scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE),
_scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME),
_motionBehaviors(AVATAR_MOTION_DEFAULTS),
_characterController(this),
- _lookAtTargetAvatar(),
- _shouldRender(true),
_eyeContactTarget(LEFT_EYE),
_realWorldFieldOfView("realWorldFieldOfView",
DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
@@ -129,6 +121,14 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
_audioListenerMode(FROM_HEAD),
_hmdAtRestDetector(glm::vec3(0), glm::quat())
{
+
+ // give the pointer to our head to inherited _headData variable from AvatarData
+ _headData = new MyHead(this);
+
+ _skeletonModel = std::make_shared(this, nullptr, rig);
+ connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
+
+
using namespace recording;
_skeletonModel->flagAsCauterized();
@@ -536,7 +536,7 @@ void MyAvatar::simulate(float deltaTime) {
}
head->setPosition(headPosition);
head->setScale(getUniformScale());
- head->simulate(deltaTime, true);
+ head->simulate(deltaTime);
}
// Record avatars movements.
@@ -1450,12 +1450,12 @@ void MyAvatar::updateMotors() {
glm::quat motorRotation;
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
if (_characterController.getState() == CharacterController::State::Hover) {
- motorRotation = getHead()->getCameraOrientation();
+ motorRotation = getMyHead()->getCameraOrientation();
} else {
// non-hovering = walking: follow camera twist about vertical but not lift
// so we decompose camera's rotation and store the twist part in motorRotation
glm::quat liftRotation;
- swingTwistDecomposition(getHead()->getCameraOrientation(), _worldUpDirection, liftRotation, motorRotation);
+ swingTwistDecomposition(getMyHead()->getCameraOrientation(), _worldUpDirection, liftRotation, motorRotation);
}
const float DEFAULT_MOTOR_TIMESCALE = 0.2f;
const float INVALID_MOTOR_TIMESCALE = 1.0e6f;
@@ -1469,7 +1469,7 @@ void MyAvatar::updateMotors() {
}
if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) {
if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) {
- motorRotation = getHead()->getCameraOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
+ motorRotation = getMyHead()->getCameraOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
} else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) {
motorRotation = getOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
} else {
@@ -1814,7 +1814,7 @@ void MyAvatar::updateOrientation(float deltaTime) {
if (getCharacterController()->getState() == CharacterController::State::Hover) {
// This is the direction the user desires to fly in.
- glm::vec3 desiredFacing = getHead()->getCameraOrientation() * Vectors::UNIT_Z;
+ glm::vec3 desiredFacing = getMyHead()->getCameraOrientation() * Vectors::UNIT_Z;
desiredFacing.y = 0.0f;
// This is our reference frame, it is captured when the user begins to move.
@@ -1958,7 +1958,7 @@ void MyAvatar::updatePosition(float deltaTime) {
if (!_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) > 0.1f || fabs(getDriveKey(TRANSLATE_X)) > 0.1f)) {
_hoverReferenceCameraFacingIsCaptured = true;
// transform the camera facing vector into sensor space.
- _hoverReferenceCameraFacing = transformVectorFast(glm::inverse(_sensorToWorldMatrix), getHead()->getCameraOrientation() * Vectors::UNIT_Z);
+ _hoverReferenceCameraFacing = transformVectorFast(glm::inverse(_sensorToWorldMatrix), getMyHead()->getCameraOrientation() * Vectors::UNIT_Z);
} else if (_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) <= 0.1f && fabs(getDriveKey(TRANSLATE_X)) <= 0.1f)) {
_hoverReferenceCameraFacingIsCaptured = false;
}
@@ -2804,3 +2804,7 @@ void MyAvatar::updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose&
});
}
}
+
+const MyHead* MyAvatar::getMyHead() const {
+ return static_cast(getHead());
+}
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 6a1e457a97..7c510f0556 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -22,14 +22,15 @@
#include
#include
+#include
-#include "Avatar.h"
#include "AtRestDetector.h"
#include "MyCharacterController.h"
#include
class AvatarActionHold;
class ModelItemID;
+class MyHead;
enum eyeContactTarget {
LEFT_EYE,
@@ -149,6 +150,7 @@ public:
explicit MyAvatar(QThread* thread, RigPointer rig);
~MyAvatar();
+ void instantiableAvatar() override {};
void registerMetaTypes(QScriptEngine* engine);
virtual void simulateAttachments(float deltaTime) override;
@@ -353,6 +355,7 @@ public:
eyeContactTarget getEyeContactTarget();
+ const MyHead* getMyHead() const;
Q_INVOKABLE glm::vec3 getHeadPosition() const { return getHead()->getPosition(); }
Q_INVOKABLE float getHeadFinalYaw() const { return getHead()->getFinalYaw(); }
Q_INVOKABLE float getHeadFinalRoll() const { return getHead()->getFinalRoll(); }
@@ -589,17 +592,17 @@ private:
std::array _driveKeys;
std::bitset _disabledDriveKeys;
- bool _wasPushing;
- bool _isPushing;
- bool _isBeingPushed;
- bool _isBraking;
- bool _isAway;
+ bool _wasPushing { false };
+ bool _isPushing { false };
+ bool _isBeingPushed { false };
+ bool _isBraking { false };
+ bool _isAway { false };
- float _boomLength;
+ float _boomLength { ZOOM_DEFAULT };
float _yawSpeed; // degrees/sec
float _pitchSpeed; // degrees/sec
- glm::vec3 _thrust; // impulse accumulator for outside sources
+ glm::vec3 _thrust { 0.0f }; // impulse accumulator for outside sources
glm::vec3 _actionMotorVelocity; // target local-frame velocity of avatar (default controller actions)
glm::vec3 _scriptedMotorVelocity; // target local-frame velocity of avatar (analog script)
@@ -615,7 +618,7 @@ private:
AvatarWeakPointer _lookAtTargetAvatar;
glm::vec3 _targetAvatarPosition;
- bool _shouldRender;
+ bool _shouldRender { true };
float _oculusYawOffset;
eyeContactTarget _eyeContactTarget;
diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp
new file mode 100644
index 0000000000..c41fff3bb5
--- /dev/null
+++ b/interface/src/avatar/MyHead.cpp
@@ -0,0 +1,76 @@
+//
+// Created by Bradley Austin Davis on 2017/04/27
+// Copyright 2013-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 "MyHead.h"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "devices/DdeFaceTracker.h"
+#include "Application.h"
+#include "MyAvatar.h"
+
+using namespace std;
+
+MyHead::MyHead(MyAvatar* owningAvatar) : Head(owningAvatar) {
+}
+
+glm::quat MyHead::getCameraOrientation() const {
+ // NOTE: Head::getCameraOrientation() is not used for orienting the camera "view" while in Oculus mode, so
+ // you may wonder why this code is here. This method will be called while in Oculus mode to determine how
+ // to change the driving direction while in Oculus mode. It is used to support driving toward where you're
+ // head is looking. Note that in oculus mode, your actual camera view and where your head is looking is not
+ // always the same.
+ if (qApp->isHMDMode()) {
+ MyAvatar* myAvatar = static_cast(_owningAvatar);
+ return glm::quat_cast(myAvatar->getSensorToWorldMatrix()) * myAvatar->getHMDSensorOrientation();
+ } else {
+ Avatar* owningAvatar = static_cast(_owningAvatar);
+ return owningAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f)));
+ }
+}
+
+void MyHead::simulate(float deltaTime) {
+ auto player = DependencyManager::get();
+ // Only use face trackers when not playing back a recording.
+ if (!player->isPlaying()) {
+ FaceTracker* faceTracker = qApp->getActiveFaceTracker();
+ _isFaceTrackerConnected = faceTracker != NULL && !faceTracker->isMuted();
+ if (_isFaceTrackerConnected) {
+ _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
+
+ if (typeid(*faceTracker) == typeid(DdeFaceTracker)) {
+
+ if (Menu::getInstance()->isOptionChecked(MenuOption::UseAudioForMouth)) {
+ calculateMouthShapes(deltaTime);
+
+ const int JAW_OPEN_BLENDSHAPE = 21;
+ const int MMMM_BLENDSHAPE = 34;
+ const int FUNNEL_BLENDSHAPE = 40;
+ const int SMILE_LEFT_BLENDSHAPE = 28;
+ const int SMILE_RIGHT_BLENDSHAPE = 29;
+ _blendshapeCoefficients[JAW_OPEN_BLENDSHAPE] += _audioJawOpen;
+ _blendshapeCoefficients[SMILE_LEFT_BLENDSHAPE] += _mouth4;
+ _blendshapeCoefficients[SMILE_RIGHT_BLENDSHAPE] += _mouth4;
+ _blendshapeCoefficients[MMMM_BLENDSHAPE] += _mouth2;
+ _blendshapeCoefficients[FUNNEL_BLENDSHAPE] += _mouth3;
+ }
+ applyEyelidOffset(getFinalOrientationInWorldFrame());
+ }
+ }
+ auto eyeTracker = DependencyManager::get();
+ _isEyeTrackerConnected = eyeTracker->isTracking();
+ }
+ Parent::simulate(deltaTime);
+}
\ No newline at end of file
diff --git a/interface/src/avatar/MyHead.h b/interface/src/avatar/MyHead.h
new file mode 100644
index 0000000000..097415153c
--- /dev/null
+++ b/interface/src/avatar/MyHead.h
@@ -0,0 +1,30 @@
+//
+// Created by Bradley Austin Davis on 2017/04/27
+// Copyright 2013-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_MyHead_h
+#define hifi_MyHead_h
+
+#include
+
+class MyAvatar;
+class MyHead : public Head {
+ using Parent = Head;
+public:
+ explicit MyHead(MyAvatar* owningAvatar);
+
+ /// \return orientationBody * orientationBasePitch
+ glm::quat getCameraOrientation() const;
+ void simulate(float deltaTime) override;
+
+private:
+ // disallow copies of the Head, copy of owning Avatar is disallowed too
+ MyHead(const Head&);
+ MyHead& operator= (const MyHead&);
+};
+
+#endif // hifi_MyHead_h
diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp
new file mode 100644
index 0000000000..1b9aa4dc18
--- /dev/null
+++ b/interface/src/avatar/MySkeletonModel.cpp
@@ -0,0 +1,156 @@
+//
+// Created by Bradley Austin Davis on 2017/04/27
+// Copyright 2013-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 "MySkeletonModel.h"
+
+#include
+
+#include "Application.h"
+#include "InterfaceLogging.h"
+
+MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer rig) : SkeletonModel(owningAvatar, parent, rig) {
+}
+
+Rig::CharacterControllerState convertCharacterControllerState(CharacterController::State state) {
+ switch (state) {
+ default:
+ case CharacterController::State::Ground:
+ return Rig::CharacterControllerState::Ground;
+ case CharacterController::State::Takeoff:
+ return Rig::CharacterControllerState::Takeoff;
+ case CharacterController::State::InAir:
+ return Rig::CharacterControllerState::InAir;
+ case CharacterController::State::Hover:
+ return Rig::CharacterControllerState::Hover;
+ };
+}
+
+// Called within Model::simulate call, below.
+void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
+ const FBXGeometry& geometry = getFBXGeometry();
+
+ Head* head = _owningAvatar->getHead();
+
+ // make sure lookAt is not too close to face (avoid crosseyes)
+ glm::vec3 lookAt = _owningAvatar->isMyAvatar() ? head->getLookAtPosition() : head->getCorrectedLookAtPosition();
+ MyAvatar* myAvatar = static_cast(_owningAvatar);
+
+ Rig::HeadParameters headParams;
+
+ // input action is the highest priority source for head orientation.
+ auto avatarHeadPose = myAvatar->getHeadControllerPoseInAvatarFrame();
+ if (avatarHeadPose.isValid()) {
+ glm::mat4 rigHeadMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation());
+ headParams.rigHeadPosition = extractTranslation(rigHeadMat);
+ headParams.rigHeadOrientation = glmExtractRotation(rigHeadMat);
+ headParams.headEnabled = true;
+ } else {
+ if (qApp->isHMDMode()) {
+ // get HMD position from sensor space into world space, and back into rig space
+ glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();
+ glm::mat4 rigToWorld = createMatFromQuatAndPos(getRotation(), getTranslation());
+ glm::mat4 worldToRig = glm::inverse(rigToWorld);
+ glm::mat4 rigHMDMat = worldToRig * worldHMDMat;
+ _rig->computeHeadFromHMD(AnimPose(rigHMDMat), headParams.rigHeadPosition, headParams.rigHeadOrientation);
+ headParams.headEnabled = true;
+ } else {
+ // even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and down in desktop mode.
+ // preMult 180 is necessary to convert from avatar to rig coordinates.
+ // postMult 180 is necessary to convert head from -z forward to z forward.
+ headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180;
+ headParams.headEnabled = false;
+ }
+ }
+
+ auto avatarHipsPose = myAvatar->getHipsControllerPoseInAvatarFrame();
+ if (avatarHipsPose.isValid()) {
+ glm::mat4 rigHipsMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarHipsPose.getRotation(), avatarHipsPose.getTranslation());
+ headParams.hipsMatrix = rigHipsMat;
+ headParams.hipsEnabled = true;
+ } else {
+ headParams.hipsEnabled = false;
+ }
+
+ auto avatarSpine2Pose = myAvatar->getSpine2ControllerPoseInAvatarFrame();
+ if (avatarSpine2Pose.isValid()) {
+ glm::mat4 rigSpine2Mat = Matrices::Y_180 * createMatFromQuatAndPos(avatarSpine2Pose.getRotation(), avatarSpine2Pose.getTranslation());
+ headParams.spine2Matrix = rigSpine2Mat;
+ headParams.spine2Enabled = true;
+ } else {
+ headParams.spine2Enabled = false;
+ }
+
+ headParams.isTalking = head->getTimeWithoutTalking() <= 1.5f;
+
+ _rig->updateFromHeadParameters(headParams, deltaTime);
+
+ Rig::HandAndFeetParameters handAndFeetParams;
+
+ auto leftPose = myAvatar->getLeftHandControllerPoseInAvatarFrame();
+ if (leftPose.isValid()) {
+ handAndFeetParams.isLeftEnabled = true;
+ handAndFeetParams.leftPosition = Quaternions::Y_180 * leftPose.getTranslation();
+ handAndFeetParams.leftOrientation = Quaternions::Y_180 * leftPose.getRotation();
+ } else {
+ handAndFeetParams.isLeftEnabled = false;
+ }
+
+ auto rightPose = myAvatar->getRightHandControllerPoseInAvatarFrame();
+ if (rightPose.isValid()) {
+ handAndFeetParams.isRightEnabled = true;
+ handAndFeetParams.rightPosition = Quaternions::Y_180 * rightPose.getTranslation();
+ handAndFeetParams.rightOrientation = Quaternions::Y_180 * rightPose.getRotation();
+ } else {
+ handAndFeetParams.isRightEnabled = false;
+ }
+
+ auto leftFootPose = myAvatar->getLeftFootControllerPoseInAvatarFrame();
+ if (leftFootPose.isValid()) {
+ handAndFeetParams.isLeftFootEnabled = true;
+ handAndFeetParams.leftFootPosition = Quaternions::Y_180 * leftFootPose.getTranslation();
+ handAndFeetParams.leftFootOrientation = Quaternions::Y_180 * leftFootPose.getRotation();
+ } else {
+ handAndFeetParams.isLeftFootEnabled = false;
+ }
+
+ auto rightFootPose = myAvatar->getRightFootControllerPoseInAvatarFrame();
+ if (rightFootPose.isValid()) {
+ handAndFeetParams.isRightFootEnabled = true;
+ handAndFeetParams.rightFootPosition = Quaternions::Y_180 * rightFootPose.getTranslation();
+ handAndFeetParams.rightFootOrientation = Quaternions::Y_180 * rightFootPose.getRotation();
+ } else {
+ handAndFeetParams.isRightFootEnabled = false;
+ }
+
+ handAndFeetParams.bodyCapsuleRadius = myAvatar->getCharacterController()->getCapsuleRadius();
+ handAndFeetParams.bodyCapsuleHalfHeight = myAvatar->getCharacterController()->getCapsuleHalfHeight();
+ handAndFeetParams.bodyCapsuleLocalOffset = myAvatar->getCharacterController()->getCapsuleLocalOffset();
+
+ _rig->updateFromHandAndFeetParameters(handAndFeetParams, deltaTime);
+
+ Rig::CharacterControllerState ccState = convertCharacterControllerState(myAvatar->getCharacterController()->getState());
+
+ auto velocity = myAvatar->getLocalVelocity();
+ auto position = myAvatar->getLocalPosition();
+ auto orientation = myAvatar->getLocalOrientation();
+ _rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState);
+
+ Rig::EyeParameters eyeParams;
+ eyeParams.eyeLookAt = lookAt;
+ eyeParams.eyeSaccade = head->getSaccade();
+ eyeParams.modelRotation = getRotation();
+ eyeParams.modelTranslation = getTranslation();
+ eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex;
+ eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex;
+
+ _rig->updateFromEyeParameters(eyeParams);
+
+ // evaluate AnimGraph animation and update jointStates.
+ Parent::updateRig(deltaTime, parentTransform);
+}
+
diff --git a/interface/src/avatar/MySkeletonModel.h b/interface/src/avatar/MySkeletonModel.h
new file mode 100644
index 0000000000..84fccc825a
--- /dev/null
+++ b/interface/src/avatar/MySkeletonModel.h
@@ -0,0 +1,26 @@
+//
+// Created by Bradley Austin Davis on 2017/04/27
+// Copyright 2013-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_MySkeletonModel_h
+#define hifi_MySkeletonModel_h
+
+#include
+
+/// A skeleton loaded from a model.
+class MySkeletonModel : public SkeletonModel {
+ Q_OBJECT
+
+private:
+ using Parent = SkeletonModel;
+
+public:
+ MySkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr, RigPointer rig = nullptr);
+ void updateRig(float deltaTime, glm::mat4 parentTransform) override;
+};
+
+#endif // hifi_MySkeletonModel_h
diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp
index fa7b2c173e..ed52083d77 100644
--- a/interface/src/devices/DdeFaceTracker.cpp
+++ b/interface/src/devices/DdeFaceTracker.cpp
@@ -9,20 +9,21 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "DdeFaceTracker.h"
+
#include
-#include
-#include
-#include
-#include
-#include
+#include
+#include
+#include
+#include
+#include
#include
#include
+#include
#include "Application.h"
-#include "DdeFaceTracker.h"
-#include "FaceshiftConstants.h"
#include "InterfaceLogging.h"
#include "Menu.h"
diff --git a/interface/src/devices/DdeFaceTracker.h b/interface/src/devices/DdeFaceTracker.h
index f125dfc3cf..dfb9c6d638 100644
--- a/interface/src/devices/DdeFaceTracker.h
+++ b/interface/src/devices/DdeFaceTracker.h
@@ -12,6 +12,8 @@
#ifndef hifi_DdeFaceTracker_h
#define hifi_DdeFaceTracker_h
+#include
+
#if defined(Q_OS_WIN) || defined(Q_OS_OSX)
#define HAVE_DDE
#endif
diff --git a/interface/src/devices/Leapmotion.cpp b/interface/src/devices/Leapmotion.cpp
index cb99cf324d..c643042300 100644
--- a/interface/src/devices/Leapmotion.cpp
+++ b/interface/src/devices/Leapmotion.cpp
@@ -1,7 +1,4 @@
//
-// Leapmotion.cpp
-// interface/src/devices
-//
// Created by Sam Cake on 6/2/2014
// Copyright 2014 High Fidelity, Inc.
//
@@ -10,10 +7,11 @@
//
#include "Leapmotion.h"
-#include "Menu.h"
#include
+#include "Menu.h"
+
const int PALMROOT_NUM_JOINTS = 3;
const int FINGER_NUM_JOINTS = 4;
const int HAND_NUM_JOINTS = FINGER_NUM_JOINTS*5+PALMROOT_NUM_JOINTS;
diff --git a/interface/src/devices/Leapmotion.h b/interface/src/devices/Leapmotion.h
index 6ecec8ccf9..a119821846 100644
--- a/interface/src/devices/Leapmotion.h
+++ b/interface/src/devices/Leapmotion.h
@@ -1,7 +1,4 @@
//
-// Leapmotion.h
-// interface/src/devices
-//
// Created by Sam Cake on 6/2/2014
// Copyright 2014 High Fidelity, Inc.
//
diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h
index 34b2cbca8b..bc9955a24f 100644
--- a/interface/src/ui/AvatarInputs.h
+++ b/interface/src/ui/AvatarInputs.h
@@ -34,7 +34,7 @@ class AvatarInputs : public QQuickItem {
public:
static AvatarInputs* getInstance();
- float loudnessToAudioLevel(float loudness);
+ Q_INVOKABLE float loudnessToAudioLevel(float loudness);
AvatarInputs(QQuickItem* parent = nullptr);
void update();
bool showAudioTools() const { return _showAudioTools; }
diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp
index e2e22fe366..767c122bb6 100644
--- a/interface/src/ui/PreferencesDialog.cpp
+++ b/interface/src/ui/PreferencesDialog.cpp
@@ -330,6 +330,30 @@ void setupPreferences() {
preferences->addPreference(preference);
}
}
+ {
+ auto getter = []()->bool { return image::isColorTexturesCompressionEnabled(); };
+ auto setter = [](bool value) { return image::setColorTexturesCompressionEnabled(value); };
+ auto preference = new CheckPreference(RENDER, "Compress Color Textures", getter, setter);
+ preferences->addPreference(preference);
+ }
+ {
+ auto getter = []()->bool { return image::isNormalTexturesCompressionEnabled(); };
+ auto setter = [](bool value) { return image::setNormalTexturesCompressionEnabled(value); };
+ auto preference = new CheckPreference(RENDER, "Compress Normal Textures", getter, setter);
+ preferences->addPreference(preference);
+ }
+ {
+ auto getter = []()->bool { return image::isGrayscaleTexturesCompressionEnabled(); };
+ auto setter = [](bool value) { return image::setGrayscaleTexturesCompressionEnabled(value); };
+ auto preference = new CheckPreference(RENDER, "Compress Grayscale Textures", getter, setter);
+ preferences->addPreference(preference);
+ }
+ {
+ auto getter = []()->bool { return image::isCubeTexturesCompressionEnabled(); };
+ auto setter = [](bool value) { return image::setCubeTexturesCompressionEnabled(value); };
+ auto preference = new CheckPreference(RENDER, "Compress Cube Textures", getter, setter);
+ preferences->addPreference(preference);
+ }
}
{
static const QString RENDER("Networking");
diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp
index d7057c6faa..6f1167cfc9 100644
--- a/interface/src/ui/overlays/Base3DOverlay.cpp
+++ b/interface/src/ui/overlays/Base3DOverlay.cpp
@@ -81,6 +81,10 @@ QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& propert
void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
QVariantMap properties = originalProperties;
+ if (properties["name"].isValid()) {
+ setName(properties["name"].toString());
+ }
+
// carry over some legacy keys
if (!properties["position"].isValid() && !properties["localPosition"].isValid()) {
if (properties["p1"].isValid()) {
@@ -207,6 +211,9 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
}
QVariant Base3DOverlay::getProperty(const QString& property) {
+ if (property == "name") {
+ return _name;
+ }
if (property == "position" || property == "start" || property == "p1" || property == "point") {
return vec3toVariant(getPosition());
}
diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h
index a1c23e5cd8..29d4c093a9 100644
--- a/interface/src/ui/overlays/Base3DOverlay.h
+++ b/interface/src/ui/overlays/Base3DOverlay.h
@@ -26,6 +26,9 @@ public:
virtual OverlayID getOverlayID() const override { return OverlayID(getID().toString()); }
void setOverlayID(OverlayID overlayID) override { setID(overlayID); }
+ virtual QString getName() const override { return QString("Overlay:") + _name; }
+ void setName(QString name) { _name = name; }
+
// getters
virtual bool is3D() const override { return true; }
@@ -74,6 +77,8 @@ protected:
bool _drawInFront;
bool _isAA;
bool _isGrabbable { false };
+
+ QString _name;
};
#endif // hifi_Base3DOverlay_h
diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp
index e993166558..307f23bff3 100644
--- a/interface/src/ui/overlays/ModelOverlay.cpp
+++ b/interface/src/ui/overlays/ModelOverlay.cpp
@@ -22,6 +22,7 @@ ModelOverlay::ModelOverlay()
_modelTextures(QVariantMap())
{
_model->init();
+ _model->setLoadingPriority(_loadPriority);
_isLoaded = false;
}
@@ -30,9 +31,11 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) :
_model(std::make_shared(std::make_shared(), nullptr, this)),
_modelTextures(QVariantMap()),
_url(modelOverlay->_url),
- _updateModel(false)
+ _updateModel(false),
+ _loadPriority(modelOverlay->getLoadPriority())
{
_model->init();
+ _model->setLoadingPriority(_loadPriority);
if (_url.isValid()) {
_updateModel = true;
_isLoaded = false;
@@ -113,6 +116,12 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
_updateModel = true;
}
+ auto loadPriorityProperty = properties["loadPriority"];
+ if (loadPriorityProperty.isValid()) {
+ _loadPriority = loadPriorityProperty.toFloat();
+ _model->setLoadingPriority(_loadPriority);
+ }
+
auto urlValue = properties["url"];
if (urlValue.isValid() && urlValue.canConvert()) {
_url = urlValue.toString();
@@ -279,3 +288,10 @@ void ModelOverlay::locationChanged(bool tellPhysics) {
_model->setTranslation(getPosition());
}
}
+
+QString ModelOverlay::getName() const {
+ if (_name != "") {
+ return QString("Overlay:") + getType() + ":" + _name;
+ }
+ return QString("Overlay:") + getType() + ":" + _url.toString();
+}
diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h
index 8afe9a20b6..59548dfe62 100644
--- a/interface/src/ui/overlays/ModelOverlay.h
+++ b/interface/src/ui/overlays/ModelOverlay.h
@@ -22,6 +22,8 @@ public:
static QString const TYPE;
virtual QString getType() const override { return TYPE; }
+ virtual QString getName() const override;
+
ModelOverlay();
ModelOverlay(const ModelOverlay* modelOverlay);
@@ -41,6 +43,8 @@ public:
void locationChanged(bool tellPhysics) override;
+ float getLoadPriority() const { return _loadPriority; }
+
protected:
// helper to extract metadata from our Model's rigged joints
template using mapFunction = std::function;
@@ -55,6 +59,7 @@ private:
QUrl _url;
bool _updateModel = { false };
bool _scaleToFit = { false };
+ float _loadPriority { 0.0f };
};
#endif // hifi_ModelOverlay_h
diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp
index fedead5aa5..ecc63801fc 100644
--- a/interface/src/ui/overlays/Web3DOverlay.cpp
+++ b/interface/src/ui/overlays/Web3DOverlay.cpp
@@ -421,6 +421,13 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) {
return;
}
+ //do not send secondary button events to tablet
+ if (event.getButton() == PointerEvent::SecondaryButton ||
+ //do not block composed events
+ event.getButtons() == PointerEvent::SecondaryButton) {
+ return;
+ }
+
QTouchEvent::TouchPoint point;
point.setId(event.getID());
point.setState(touchPointState);
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index b684aac89c..680e9129aa 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -1097,28 +1097,27 @@ void AudioClient::handleRecordedAudioInput(const QByteArray& audio) {
}
void AudioClient::prepareLocalAudioInjectors() {
- if (_outputPeriod == 0) {
- return;
- }
-
- int bufferCapacity = _localInjectorsStream.getSampleCapacity();
- if (_localToOutputResampler) {
- // avoid overwriting the buffer,
- // instead of failing on writes because the buffer is used as a lock-free pipe
- bufferCapacity -=
- _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL) *
- AudioConstants::STEREO;
- bufferCapacity += 1;
- }
-
int samplesNeeded = std::numeric_limits::max();
while (samplesNeeded > 0) {
- // lock for every write to avoid locking out the device callback
- // this lock is intentional - the buffer is only lock-free in its use in the device callback
- RecursiveLock lock(_localAudioMutex);
+ // unlock between every write to allow device switching
+ Lock lock(_localAudioMutex);
+
+ // in case of a device switch, consider bufferCapacity volatile across iterations
+ if (_outputPeriod == 0) {
+ return;
+ }
+
+ int bufferCapacity = _localInjectorsStream.getSampleCapacity();
+ int maxOutputSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * AudioConstants::STEREO;
+ if (_localToOutputResampler) {
+ maxOutputSamples =
+ _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL) *
+ AudioConstants::STEREO;
+ }
samplesNeeded = bufferCapacity - _localSamplesAvailable.load(std::memory_order_relaxed);
- if (samplesNeeded <= 0) {
+ if (samplesNeeded < maxOutputSamples) {
+ // avoid overwriting the buffer to prevent losing frames
break;
}
@@ -1168,16 +1167,18 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
memset(mixBuffer, 0, AudioConstants::NETWORK_FRAME_SAMPLES_STEREO * sizeof(float));
for (AudioInjector* injector : _activeLocalAudioInjectors) {
- if (injector->getLocalBuffer()) {
+ // the lock guarantees that injectorBuffer, if found, is invariant
+ AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
+ if (injectorBuffer) {
static const int HRTF_DATASET_INDEX = 1;
int numChannels = injector->isAmbisonic() ? AudioConstants::AMBISONIC : (injector->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO);
- qint64 bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
+ size_t bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
// get one frame from the injector
memset(_localScratchBuffer, 0, bytesToRead);
- if (0 < injector->getLocalBuffer()->readData((char*)_localScratchBuffer, bytesToRead)) {
+ if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
if (injector->isAmbisonic()) {
@@ -1317,15 +1318,17 @@ void AudioClient::setIsStereoInput(bool isStereoInput) {
}
bool AudioClient::outputLocalInjector(AudioInjector* injector) {
- Lock lock(_injectorsMutex);
- if (injector->getLocalBuffer() && _audioInput ) {
- // just add it to the vector of active local injectors, if
- // not already there.
- // Since this is invoked with invokeMethod, there _should_ be
- // no reason to lock access to the vector of injectors.
+ AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
+ if (injectorBuffer) {
+ // local injectors are on the AudioInjectorsThread, so we must guard access
+ Lock lock(_injectorsMutex);
if (!_activeLocalAudioInjectors.contains(injector)) {
qCDebug(audioclient) << "adding new injector";
_activeLocalAudioInjectors.append(injector);
+
+ // move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop())
+ injectorBuffer->setParent(nullptr);
+ injectorBuffer->moveToThread(&_localAudioThread);
} else {
qCDebug(audioclient) << "injector exists in active list already";
}
@@ -1333,7 +1336,7 @@ bool AudioClient::outputLocalInjector(AudioInjector* injector) {
return true;
} else {
- // no local buffer or audio
+ // no local buffer
return false;
}
}
@@ -1452,7 +1455,7 @@ void AudioClient::outputNotify() {
bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) {
bool supportedFormat = false;
- RecursiveLock lock(_localAudioMutex);
+ Lock lock(_localAudioMutex);
_localSamplesAvailable.exchange(0, std::memory_order_release);
// cleanup any previously initialized device
@@ -1681,8 +1684,12 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
int injectorSamplesPopped = 0;
{
- RecursiveLock lock(_audio->_localAudioMutex);
bool append = networkSamplesPopped > 0;
+ // this does not require a lock as of the only two functions adding to _localSamplesAvailable (samples count):
+ // - prepareLocalAudioInjectors will only increase samples count
+ // - switchOutputToAudioDevice will zero samples count
+ // stop the device, so that readData will exhaust the existing buffer or see a zeroed samples count
+ // and start the device, which can only see a zeroed samples count
samplesRequested = std::min(samplesRequested, _audio->_localSamplesAvailable.load(std::memory_order_acquire));
if ((injectorSamplesPopped = _localInjectorsStream.appendSamples(mixBuffer, samplesRequested, append)) > 0) {
_audio->_localSamplesAvailable.fetch_sub(injectorSamplesPopped, std::memory_order_release);
diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
index 139749e8e8..aaedee7456 100644
--- a/libraries/audio-client/src/AudioClient.h
+++ b/libraries/audio-client/src/AudioClient.h
@@ -96,8 +96,6 @@ public:
using AudioPositionGetter = std::function;
using AudioOrientationGetter = std::function;
- using RecursiveMutex = std::recursive_mutex;
- using RecursiveLock = std::unique_lock;
using Mutex = std::mutex;
using Lock = std::unique_lock;
@@ -345,7 +343,7 @@ private:
int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
float* _localOutputMixBuffer { NULL };
AudioInjectorsThread _localAudioThread;
- RecursiveMutex _localAudioMutex;
+ Mutex _localAudioMutex;
// for output audio (used by this thread)
int _outputPeriod { 0 };
diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h
index d61b8d60ca..2e4611cd4e 100644
--- a/libraries/audio/src/AbstractAudioInterface.h
+++ b/libraries/audio/src/AbstractAudioInterface.h
@@ -33,7 +33,11 @@ public:
PacketType packetType, QString codecName = QString(""));
public slots:
+ // threadsafe
+ // moves injector->getLocalBuffer() to another thread (so removes its parent)
+ // take care to delete it when ~AudioInjector, as parenting Qt semantics will not work
virtual bool outputLocalInjector(AudioInjector* injector) = 0;
+
virtual bool shouldLoopbackInjectors() { return false; }
virtual void setIsStereoInput(bool stereo) = 0;
diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp
index 86cbc98d84..ce98225190 100644
--- a/libraries/audio/src/AudioInjector.cpp
+++ b/libraries/audio/src/AudioInjector.cpp
@@ -51,6 +51,10 @@ AudioInjector::AudioInjector(const QByteArray& audioData, const AudioInjectorOpt
{
}
+AudioInjector::~AudioInjector() {
+ deleteLocalBuffer();
+}
+
bool AudioInjector::stateHas(AudioInjectorState state) const {
return (_state & state) == state;
}
@@ -87,11 +91,7 @@ void AudioInjector::finish() {
emit finished();
- if (_localBuffer) {
- _localBuffer->stop();
- _localBuffer->deleteLater();
- _localBuffer = NULL;
- }
+ deleteLocalBuffer();
if (stateHas(AudioInjectorState::PendingDelete)) {
// we've been asked to delete after finishing, trigger a deleteLater here
@@ -163,7 +163,7 @@ bool AudioInjector::injectLocally() {
if (_localAudioInterface) {
if (_audioData.size() > 0) {
- _localBuffer = new AudioInjectorLocalBuffer(_audioData, this);
+ _localBuffer = new AudioInjectorLocalBuffer(_audioData);
_localBuffer->open(QIODevice::ReadOnly);
_localBuffer->setShouldLoop(_options.loop);
@@ -172,7 +172,8 @@ bool AudioInjector::injectLocally() {
_localBuffer->setCurrentOffset(_currentSendOffset);
// call this function on the AudioClient's thread
- success = QMetaObject::invokeMethod(_localAudioInterface, "outputLocalInjector", Q_ARG(AudioInjector*, this));
+ // this will move the local buffer's thread to the LocalInjectorThread
+ success = _localAudioInterface->outputLocalInjector(this);
if (!success) {
qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface";
@@ -185,6 +186,14 @@ bool AudioInjector::injectLocally() {
return success;
}
+void AudioInjector::deleteLocalBuffer() {
+ if (_localBuffer) {
+ _localBuffer->stop();
+ _localBuffer->deleteLater();
+ _localBuffer = nullptr;
+ }
+}
+
const uchar MAX_INJECTOR_VOLUME = packFloatGainToByte(1.0f);
static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1;
static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0;
diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h
index 7d57708738..a901c2520f 100644
--- a/libraries/audio/src/AudioInjector.h
+++ b/libraries/audio/src/AudioInjector.h
@@ -52,6 +52,7 @@ class AudioInjector : public QObject {
public:
AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions);
AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions);
+ ~AudioInjector();
bool isFinished() const { return (stateHas(AudioInjectorState::Finished)); }
@@ -99,6 +100,7 @@ private:
int64_t injectNextFrame();
bool inject(bool(AudioInjectorManager::*injection)(AudioInjector*));
bool injectLocally();
+ void deleteLocalBuffer();
static AbstractAudioInterface* _localAudioInterface;
diff --git a/libraries/audio/src/AudioInjectorLocalBuffer.cpp b/libraries/audio/src/AudioInjectorLocalBuffer.cpp
index 049adb0cc6..7834644baf 100644
--- a/libraries/audio/src/AudioInjectorLocalBuffer.cpp
+++ b/libraries/audio/src/AudioInjectorLocalBuffer.cpp
@@ -11,8 +11,7 @@
#include "AudioInjectorLocalBuffer.h"
-AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(const QByteArray& rawAudioArray, QObject* parent) :
- QIODevice(parent),
+AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(const QByteArray& rawAudioArray) :
_rawAudioArray(rawAudioArray),
_shouldLoop(false),
_isStopped(false),
diff --git a/libraries/audio/src/AudioInjectorLocalBuffer.h b/libraries/audio/src/AudioInjectorLocalBuffer.h
index 07d8ae5b9f..673966ff09 100644
--- a/libraries/audio/src/AudioInjectorLocalBuffer.h
+++ b/libraries/audio/src/AudioInjectorLocalBuffer.h
@@ -19,7 +19,7 @@
class AudioInjectorLocalBuffer : public QIODevice {
Q_OBJECT
public:
- AudioInjectorLocalBuffer(const QByteArray& rawAudioArray, QObject* parent);
+ AudioInjectorLocalBuffer(const QByteArray& rawAudioArray);
void stop();
diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt
index bee2cb31d6..b13bc0a4a6 100644
--- a/libraries/avatars-renderer/CMakeLists.txt
+++ b/libraries/avatars-renderer/CMakeLists.txt
@@ -1,6 +1,6 @@
set(TARGET_NAME avatars-renderer)
AUTOSCRIBE_SHADER_LIB(gpu model render render-utils)
setup_hifi_library(Widgets Network Script)
-link_hifi_libraries(shared gpu model animation physics model-networking script-engine render render-utils)
+link_hifi_libraries(shared gpu model animation physics model-networking script-engine render image render-utils)
target_bullet()
diff --git a/interface/src/avatar/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
similarity index 95%
rename from interface/src/avatar/Avatar.cpp
rename to libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
index 3bd4c663d2..be55653f64 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
@@ -27,16 +27,13 @@
#include
#include
#include
+#include
+#include
-#include "AvatarMotionState.h"
-#include "Camera.h"
-#include "InterfaceLogging.h"
-#include "SceneScriptingInterface.h"
-#include "SoftAttachmentModel.h"
+#include "Logging.h"
using namespace std;
-const glm::vec3 DEFAULT_UP_DIRECTION(0.0f, 1.0f, 0.0f);
const int NUM_BODY_CONE_SIDES = 9;
const float CHAT_MESSAGE_SCALE = 0.0015f;
const float CHAT_MESSAGE_HEIGHT = 0.1f;
@@ -71,6 +68,11 @@ namespace render {
}
}
+bool showAvatars { true };
+void Avatar::setShowAvatars(bool render) {
+ showAvatars = render;
+}
+
static bool showReceiveStats = false;
void Avatar::setShowReceiveStats(bool receiveStats) {
showReceiveStats = receiveStats;
@@ -97,25 +99,6 @@ void Avatar::setShowNamesAboveHeads(bool show) {
}
Avatar::Avatar(QThread* thread, RigPointer rig) :
- AvatarData(),
- _skeletonOffset(0.0f),
- _bodyYawDelta(0.0f),
- _positionDeltaAccumulator(0.0f),
- _lastVelocity(0.0f),
- _acceleration(0.0f),
- _lastAngularVelocity(0.0f),
- _lastOrientation(),
- _worldUpDirection(DEFAULT_UP_DIRECTION),
- _moving(false),
- _smoothPositionTime(SMOOTH_TIME_POSITION),
- _smoothPositionTimer(std::numeric_limits::max()),
- _smoothOrientationTime(SMOOTH_TIME_ORIENTATION),
- _smoothOrientationTimer(std::numeric_limits::max()),
- _smoothPositionInitial(),
- _smoothPositionTarget(),
- _smoothOrientationInitial(),
- _smoothOrientationTarget(),
- _initialized(false),
_voiceSphereID(GeometryCache::UNKNOWN_ID)
{
// we may have been created in the network thread, but we live in the main thread
@@ -123,12 +106,6 @@ Avatar::Avatar(QThread* thread, RigPointer rig) :
setScale(glm::vec3(1.0f)); // avatar scale is uniform
- // give the pointer to our head to inherited _headData variable from AvatarData
- _headData = static_cast(new Head(this));
-
- _skeletonModel = std::make_shared(this, nullptr, rig);
- connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
-
auto geometryCache = DependencyManager::get();
_nameRectGeometryID = geometryCache->allocateID();
_leftPointerGeometryID = geometryCache->allocateID();
@@ -283,7 +260,7 @@ void Avatar::updateAvatarEntities() {
// and either add or update the entity.
QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(data);
if (!jsonProperties.isObject()) {
- qCDebug(interfaceapp) << "got bad avatarEntity json" << QString(data.toHex());
+ qCDebug(avatars_renderer) << "got bad avatarEntity json" << QString(data.toHex());
continue;
}
@@ -306,7 +283,7 @@ void Avatar::updateAvatarEntities() {
// NOTE: if this avatar entity is not attached to us, strip its entity script completely...
auto attachedScript = properties.getScript();
if (!isMyAvatar() && !attachedScript.isEmpty()) {
- qCDebug(interfaceapp) << "removing entity script from avatar attached entity:" << entityID << "old script:" << attachedScript;
+ qCDebug(avatars_renderer) << "removing entity script from avatar attached entity:" << entityID << "old script:" << attachedScript;
QString noScript;
properties.setScript(noScript);
}
@@ -410,7 +387,7 @@ void Avatar::simulate(float deltaTime, bool inView) {
Head* head = getHead();
head->setPosition(headPosition);
head->setScale(getUniformScale());
- head->simulate(deltaTime, false);
+ head->simulate(deltaTime);
} else {
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
_skeletonModel->simulate(deltaTime, false);
@@ -525,13 +502,14 @@ static TextRenderer3D* textRenderer(TextRendererType type) {
void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) {
auto avatarPayload = new render::Payload(self);
auto avatarPayloadPointer = Avatar::PayloadPointer(avatarPayload);
- if (_skeletonModel->addToScene(scene, transaction)) {
- _renderItemID = scene->allocateID();
- transaction.resetItem(_renderItemID, avatarPayloadPointer);
- for (auto& attachmentModel : _attachmentModels) {
- attachmentModel->addToScene(scene, transaction);
- }
+ if (_renderItemID == render::Item::INVALID_ITEM_ID) {
+ _renderItemID = scene->allocateID();
+ }
+ transaction.resetItem(_renderItemID, avatarPayloadPointer);
+ _skeletonModel->addToScene(scene, transaction);
+ for (auto& attachmentModel : _attachmentModels) {
+ attachmentModel->addToScene(scene, transaction);
}
}
@@ -748,12 +726,12 @@ float Avatar::getBoundingRadius() const {
#ifdef DEBUG
void debugValue(const QString& str, const glm::vec3& value) {
if (glm::any(glm::isnan(value)) || glm::any(glm::isinf(value))) {
- qCWarning(interfaceapp) << "debugValue() " << str << value;
+ qCWarning(avatars_renderer) << "debugValue() " << str << value;
}
};
void debugValue(const QString& str, const float& value) {
if (glm::isnan(value) || glm::isinf(value)) {
- qCWarning(interfaceapp) << "debugValue() " << str << value;
+ qCWarning(avatars_renderer) << "debugValue() " << str << value;
}
};
#define DEBUG_VALUE(str, value) debugValue(str, value)
@@ -783,7 +761,7 @@ glm::vec3 Avatar::getDisplayNamePosition() const {
}
if (glm::any(glm::isnan(namePosition)) || glm::any(glm::isinf(namePosition))) {
- qCWarning(interfaceapp) << "Invalid display name position" << namePosition
+ qCWarning(avatars_renderer) << "Invalid display name position" << namePosition
<< ", setting is to (0.0f, 0.5f, 0.0f)";
namePosition = glm::vec3(0.0f, 0.5f, 0.0f);
}
@@ -1115,14 +1093,14 @@ void Avatar::setModelURLFinished(bool success) {
const int MAX_SKELETON_DOWNLOAD_ATTEMPTS = 4; // NOTE: we don't want to be as generous as ResourceCache is, we only want 4 attempts
if (_skeletonModel->getResourceDownloadAttemptsRemaining() <= 0 ||
_skeletonModel->getResourceDownloadAttempts() > MAX_SKELETON_DOWNLOAD_ATTEMPTS) {
- qCWarning(interfaceapp) << "Using default after failing to load Avatar model: " << _skeletonModelURL
+ qCWarning(avatars_renderer) << "Using default after failing to load Avatar model: " << _skeletonModelURL
<< "after" << _skeletonModel->getResourceDownloadAttempts() << "attempts.";
// call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that
// we don't redo this every time we receive an identity packet from the avatar with the bad url.
QMetaObject::invokeMethod(_skeletonModel.get(), "setURL",
Qt::QueuedConnection, Q_ARG(QUrl, AvatarData::defaultFullAvatarModelUrl()));
} else {
- qCWarning(interfaceapp) << "Avatar model: " << _skeletonModelURL
+ qCWarning(avatars_renderer) << "Avatar model: " << _skeletonModelURL
<< "failed to load... attempts:" << _skeletonModel->getResourceDownloadAttempts()
<< "out of:" << MAX_SKELETON_DOWNLOAD_ATTEMPTS;
}
@@ -1438,7 +1416,7 @@ void Avatar::setParentID(const QUuid& parentID) {
if (success) {
setTransform(beforeChangeTransform, success);
if (!success) {
- qCDebug(interfaceapp) << "Avatar::setParentID failed to reset avatar's location.";
+ qCDebug(avatars_renderer) << "Avatar::setParentID failed to reset avatar's location.";
}
if (initialParentID != parentID) {
_parentChanged = usecTimestampNow();
@@ -1456,7 +1434,7 @@ void Avatar::setParentJointIndex(quint16 parentJointIndex) {
if (success) {
setTransform(beforeChangeTransform, success);
if (!success) {
- qCDebug(interfaceapp) << "Avatar::setParentJointIndex failed to reset avatar's location.";
+ qCDebug(avatars_renderer) << "Avatar::setParentJointIndex failed to reset avatar's location.";
}
}
}
@@ -1488,7 +1466,7 @@ QList Avatar::getSkeleton() {
void Avatar::addToScene(AvatarSharedPointer myHandle, const render::ScenePointer& scene) {
if (scene) {
auto nodelist = DependencyManager::get();
- if (DependencyManager::get()->shouldRenderAvatars()
+ if (showAvatars
&& !nodelist->isIgnoringNode(getSessionUUID())
&& !nodelist->isRadiusIgnoringNode(getSessionUUID())) {
render::Transaction transaction;
@@ -1496,7 +1474,7 @@ void Avatar::addToScene(AvatarSharedPointer myHandle, const render::ScenePointer
scene->enqueueTransaction(transaction);
}
} else {
- qCWarning(interfaceapp) << "Avatar::addAvatar() : Unexpected null scene, possibly during application shutdown";
+ qCWarning(avatars_renderer) << "Avatar::addAvatar() : Unexpected null scene, possibly during application shutdown";
}
}
diff --git a/interface/src/avatar/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
similarity index 96%
rename from interface/src/avatar/Avatar.h
rename to libraries/avatars-renderer/src/avatars-renderer/Avatar.h
index f86bf35bd9..20704a08b2 100644
--- a/interface/src/avatar/Avatar.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
#include "Head.h"
@@ -68,6 +69,7 @@ class Avatar : public AvatarData {
Q_PROPERTY(glm::vec3 skeletonOffset READ getSkeletonOffset WRITE setSkeletonOffset)
public:
+ static void setShowAvatars(bool render);
static void setShowReceiveStats(bool receiveStats);
static void setShowMyLookAtVectors(bool showMine);
static void setShowOtherLookAtVectors(bool showOthers);
@@ -77,6 +79,8 @@ public:
explicit Avatar(QThread* thread, RigPointer rig = nullptr);
~Avatar();
+ virtual void instantiableAvatar() = 0;
+
typedef render::Payload Payload;
typedef std::shared_ptr PayloadPointer;
@@ -251,7 +255,6 @@ public:
bool isInScene() const { return render::Item::isValidID(_renderItemID); }
bool isMoving() const { return _moving; }
- //void setMotionState(AvatarMotionState* motionState);
void setPhysicsCallback(AvatarPhysicsCallback cb);
void addPhysicsFlags(uint32_t flags);
bool isInPhysicsSimulation() const { return _physicsCallback != nullptr; }
@@ -268,7 +271,6 @@ public slots:
void setModelURLFinished(bool success);
protected:
-
const float SMOOTH_TIME_POSITION = 0.125f;
const float SMOOTH_TIME_ORIENTATION = 0.075f;
@@ -282,7 +284,7 @@ protected:
std::vector> _attachmentsToRemove;
std::vector> _attachmentsToDelete;
- float _bodyYawDelta; // degrees/sec
+ float _bodyYawDelta { 0.0f }; // degrees/sec
// These position histories and derivatives are in the world-frame.
// The derivatives are the MEASURED results of all external and internal forces
@@ -298,9 +300,8 @@ protected:
glm::vec3 _angularAcceleration;
glm::quat _lastOrientation;
- glm::vec3 _worldUpDirection;
- float _stringLength;
- bool _moving; ///< set when position is changing
+ glm::vec3 _worldUpDirection { Vectors::UP };
+ bool _moving { false }; ///< set when position is changing
// protected methods...
bool isLookingAtMe(AvatarSharedPointer avatar) const;
@@ -336,10 +337,10 @@ protected:
RateCounter<> _jointDataSimulationRate;
// Smoothing data for blending from one position/orientation to another on remote agents.
- float _smoothPositionTime;
- float _smoothPositionTimer;
- float _smoothOrientationTime;
- float _smoothOrientationTimer;
+ float _smoothPositionTime { SMOOTH_TIME_POSITION };
+ float _smoothPositionTimer { std::numeric_limits::max() };
+ float _smoothOrientationTime { SMOOTH_TIME_ORIENTATION };
+ float _smoothOrientationTimer { std::numeric_limits::max() };
glm::vec3 _smoothPositionInitial;
glm::vec3 _smoothPositionTarget;
glm::quat _smoothOrientationInitial;
@@ -360,7 +361,7 @@ private:
int _leftPointerGeometryID { 0 };
int _rightPointerGeometryID { 0 };
int _nameRectGeometryID { 0 };
- bool _initialized;
+ bool _initialized { false };
bool _isLookAtTarget { false };
bool _isAnimatingScale { false };
diff --git a/interface/src/avatar/AvatarMotionState.cpp b/libraries/avatars-renderer/src/avatars-renderer/AvatarMotionState.cpp
similarity index 99%
rename from interface/src/avatar/AvatarMotionState.cpp
rename to libraries/avatars-renderer/src/avatars-renderer/AvatarMotionState.cpp
index ffa99e3990..0305634400 100644
--- a/interface/src/avatar/AvatarMotionState.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/AvatarMotionState.cpp
@@ -9,13 +9,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "AvatarMotionState.h"
+
#include
#include
#include
-#include "Avatar.h"
-#include "AvatarMotionState.h"
-#include "BulletUtil.h"
AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
assert(_avatar);
diff --git a/interface/src/avatar/AvatarMotionState.h b/libraries/avatars-renderer/src/avatars-renderer/AvatarMotionState.h
similarity index 98%
rename from interface/src/avatar/AvatarMotionState.h
rename to libraries/avatars-renderer/src/avatars-renderer/AvatarMotionState.h
index a8dd7327ca..f8801ddf2f 100644
--- a/interface/src/avatar/AvatarMotionState.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/AvatarMotionState.h
@@ -15,8 +15,9 @@
#include
#include
+#include
-class Avatar;
+#include "Avatar.h"
class AvatarMotionState : public ObjectMotionState {
public:
diff --git a/interface/src/avatar/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp
similarity index 77%
rename from interface/src/avatar/Head.cpp
rename to libraries/avatars-renderer/src/avatars-renderer/Head.cpp
index 16e5776d87..a90c9cd5f7 100644
--- a/interface/src/avatar/Head.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp
@@ -8,55 +8,28 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "Head.h"
+
#include
#include
#include
#include
+#include
+#include
+#include
#include
-
-#include "Application.h"
-#include "Avatar.h"
-#include "DependencyManager.h"
-#include "GeometryUtil.h"
-#include "Head.h"
-#include "Menu.h"
-#include "Util.h"
-#include "devices/DdeFaceTracker.h"
#include
+#include "Avatar.h"
+
using namespace std;
+static bool fixGaze { false };
+static bool disableEyelidAdjustment { false };
+
Head::Head(Avatar* owningAvatar) :
- HeadData((AvatarData*)owningAvatar),
- _returnHeadToCenter(false),
- _position(0.0f, 0.0f, 0.0f),
- _rotation(0.0f, 0.0f, 0.0f),
- _leftEyePosition(0.0f, 0.0f, 0.0f),
- _rightEyePosition(0.0f, 0.0f, 0.0f),
- _eyePosition(0.0f, 0.0f, 0.0f),
- _scale(1.0f),
- _lastLoudness(0.0f),
- _longTermAverageLoudness(-1.0f),
- _audioAttack(0.0f),
- _audioJawOpen(0.0f),
- _trailingAudioJawOpen(0.0f),
- _mouth2(0.0f),
- _mouth3(0.0f),
- _mouth4(0.0f),
- _mouthTime(0.0f),
- _saccade(0.0f, 0.0f, 0.0f),
- _saccadeTarget(0.0f, 0.0f, 0.0f),
- _leftEyeBlinkVelocity(0.0f),
- _rightEyeBlinkVelocity(0.0f),
- _timeWithoutTalking(0.0f),
- _deltaPitch(0.0f),
- _deltaYaw(0.0f),
- _deltaRoll(0.0f),
- _isCameraMoving(false),
- _isLookingAtMe(false),
- _lookingAtMeStarted(0),
- _wasLastLookingAtMe(0),
+ HeadData(owningAvatar),
_leftEyeLookAtID(DependencyManager::get()->allocateID()),
_rightEyeLookAtID(DependencyManager::get()->allocateID())
{
@@ -69,7 +42,7 @@ void Head::reset() {
_baseYaw = _basePitch = _baseRoll = 0.0f;
}
-void Head::simulate(float deltaTime, bool isMine) {
+void Head::simulate(float deltaTime) {
const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for
// grab the audio loudness from the owning avatar, if we have one
@@ -90,43 +63,7 @@ void Head::simulate(float deltaTime, bool isMine) {
_longTermAverageLoudness = glm::mix(_longTermAverageLoudness, _averageLoudness, glm::min(deltaTime / AUDIO_LONG_TERM_AVERAGING_SECS, 1.0f));
}
- if (isMine) {
- auto player = DependencyManager::get();
- // Only use face trackers when not playing back a recording.
- if (!player->isPlaying()) {
- FaceTracker* faceTracker = qApp->getActiveFaceTracker();
- _isFaceTrackerConnected = faceTracker != NULL && !faceTracker->isMuted();
- if (_isFaceTrackerConnected) {
- _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
-
- if (typeid(*faceTracker) == typeid(DdeFaceTracker)) {
-
- if (Menu::getInstance()->isOptionChecked(MenuOption::UseAudioForMouth)) {
- calculateMouthShapes(deltaTime);
-
- const int JAW_OPEN_BLENDSHAPE = 21;
- const int MMMM_BLENDSHAPE = 34;
- const int FUNNEL_BLENDSHAPE = 40;
- const int SMILE_LEFT_BLENDSHAPE = 28;
- const int SMILE_RIGHT_BLENDSHAPE = 29;
- _blendshapeCoefficients[JAW_OPEN_BLENDSHAPE] += _audioJawOpen;
- _blendshapeCoefficients[SMILE_LEFT_BLENDSHAPE] += _mouth4;
- _blendshapeCoefficients[SMILE_RIGHT_BLENDSHAPE] += _mouth4;
- _blendshapeCoefficients[MMMM_BLENDSHAPE] += _mouth2;
- _blendshapeCoefficients[FUNNEL_BLENDSHAPE] += _mouth3;
- }
-
- applyEyelidOffset(getFinalOrientationInWorldFrame());
- }
- }
-
- auto eyeTracker = DependencyManager::get();
- _isEyeTrackerConnected = eyeTracker->isTracking();
- }
- }
-
if (!_isFaceTrackerConnected) {
-
if (!_isEyeTrackerConnected) {
// Update eye saccades
const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f;
@@ -222,7 +159,7 @@ void Head::simulate(float deltaTime, bool isMine) {
} else {
_saccade = glm::vec3();
}
- if (Menu::getInstance()->isOptionChecked(MenuOption::FixGaze)) { // if debug menu turns off, use no saccade
+ if (fixGaze) { // if debug menu turns off, use no saccade
_saccade = glm::vec3();
}
@@ -277,7 +214,7 @@ void Head::calculateMouthShapes(float deltaTime) {
void Head::applyEyelidOffset(glm::quat headOrientation) {
// Adjusts the eyelid blendshape coefficients so that the eyelid follows the iris as the head pitches.
- if (Menu::getInstance()->isOptionChecked(MenuOption::DisableEyelidAdjustment)) {
+ if (disableEyelidAdjustment) {
return;
}
@@ -350,7 +287,7 @@ glm::vec3 Head::getCorrectedLookAtPosition() {
}
}
-void Head::setCorrectedLookAtPosition(glm::vec3 correctedLookAtPosition) {
+void Head::setCorrectedLookAtPosition(const glm::vec3& correctedLookAtPosition) {
if (!isLookingAtMe()) {
_lookingAtMeStarted = usecTimestampNow();
}
@@ -366,25 +303,6 @@ bool Head::isLookingAtMe() {
return _isLookingAtMe || (now - _wasLastLookingAtMe) < LOOKING_AT_ME_GAP_ALLOWED;
}
-glm::quat Head::getCameraOrientation() const {
- // NOTE: Head::getCameraOrientation() is not used for orienting the camera "view" while in Oculus mode, so
- // you may wonder why this code is here. This method will be called while in Oculus mode to determine how
- // to change the driving direction while in Oculus mode. It is used to support driving toward where you're
- // head is looking. Note that in oculus mode, your actual camera view and where your head is looking is not
- // always the same.
- if (qApp->isHMDMode()) {
- MyAvatar* myAvatar = dynamic_cast(_owningAvatar);
- if (myAvatar) {
- return glm::quat_cast(myAvatar->getSensorToWorldMatrix()) * myAvatar->getHMDSensorOrientation();
- } else {
- return getOrientation();
- }
- } else {
- Avatar* owningAvatar = static_cast(_owningAvatar);
- return owningAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f)));
- }
-}
-
glm::quat Head::getEyeRotation(const glm::vec3& eyePosition) const {
glm::quat orientation = getOrientation();
glm::vec3 lookAtDelta = _lookAtPosition - eyePosition;
diff --git a/interface/src/avatar/Head.h b/libraries/avatars-renderer/src/avatars-renderer/Head.h
similarity index 79%
rename from interface/src/avatar/Head.h
rename to libraries/avatars-renderer/src/avatars-renderer/Head.h
index fd20e709f5..aea6a41528 100644
--- a/interface/src/avatar/Head.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/Head.h
@@ -11,16 +11,10 @@
#ifndef hifi_Head_h
#define hifi_Head_h
-#include
-#include
-
+#include
#include
-
#include
-#include "world.h"
-
-
const float EYE_EAR_GAP = 0.08f;
class Avatar;
@@ -31,9 +25,9 @@ public:
void init();
void reset();
- void simulate(float deltaTime, bool isMine);
+ virtual void simulate(float deltaTime);
void setScale(float scale);
- void setPosition(glm::vec3 position) { _position = position; }
+ void setPosition(const glm::vec3& position) { _position = position; }
void setAverageLoudness(float averageLoudness) { _averageLoudness = averageLoudness; }
void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; }
@@ -43,17 +37,14 @@ public:
/// \return orientationBody * (orientationBase+Delta)
glm::quat getFinalOrientationInWorldFrame() const;
- /// \return orientationBody * orientationBasePitch
- glm::quat getCameraOrientation () const;
-
- void setCorrectedLookAtPosition(glm::vec3 correctedLookAtPosition);
+ void setCorrectedLookAtPosition(const glm::vec3& correctedLookAtPosition);
glm::vec3 getCorrectedLookAtPosition();
void clearCorrectedLookAtPosition() { _isLookingAtMe = false; }
bool isLookingAtMe();
quint64 getLookingAtMeStarted() { return _lookingAtMeStarted; }
float getScale() const { return _scale; }
- glm::vec3 getPosition() const { return _position; }
+ const glm::vec3& getPosition() const { return _position; }
const glm::vec3& getEyePosition() const { return _eyePosition; }
const glm::vec3& getSaccade() const { return _saccade; }
glm::vec3 getRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
@@ -91,46 +82,46 @@ public:
float getTimeWithoutTalking() const { return _timeWithoutTalking; }
-private:
+protected:
glm::vec3 calculateAverageEyePosition() const { return _leftEyePosition + (_rightEyePosition - _leftEyePosition ) * 0.5f; }
// disallow copies of the Head, copy of owning Avatar is disallowed too
Head(const Head&);
Head& operator= (const Head&);
- bool _returnHeadToCenter;
+ bool _returnHeadToCenter { false };
glm::vec3 _position;
glm::vec3 _rotation;
glm::vec3 _leftEyePosition;
glm::vec3 _rightEyePosition;
glm::vec3 _eyePosition;
- float _scale;
- float _lastLoudness;
- float _longTermAverageLoudness;
- float _audioAttack;
- float _audioJawOpen;
- float _trailingAudioJawOpen;
- float _mouth2;
- float _mouth3;
- float _mouth4;
- float _mouthTime;
+ float _scale { 1.0f };
+ float _lastLoudness { 0.0f };
+ float _longTermAverageLoudness { -1.0f };
+ float _audioAttack { 0.0f };
+ float _audioJawOpen { 0.0f };
+ float _trailingAudioJawOpen { 0.0f };
+ float _mouth2 { 0.0f };
+ float _mouth3 { 0.0f };
+ float _mouth4 { 0.0f };
+ float _mouthTime { 0.0f };
glm::vec3 _saccade;
glm::vec3 _saccadeTarget;
- float _leftEyeBlinkVelocity;
- float _rightEyeBlinkVelocity;
- float _timeWithoutTalking;
+ float _leftEyeBlinkVelocity { 0.0f };
+ float _rightEyeBlinkVelocity { 0.0f };
+ float _timeWithoutTalking { 0.0f };
// delta angles for local head rotation (driven by hardware input)
- float _deltaPitch;
- float _deltaYaw;
- float _deltaRoll;
+ float _deltaPitch { 0.0f };
+ float _deltaYaw { 0.0f };
+ float _deltaRoll { 0.0f };
- bool _isCameraMoving;
- bool _isLookingAtMe;
- quint64 _lookingAtMeStarted;
- quint64 _wasLastLookingAtMe;
+ bool _isCameraMoving { false };
+ bool _isLookingAtMe { false };
+ quint64 _lookingAtMeStarted { 0 };
+ quint64 _wasLastLookingAtMe { 0 };
glm::vec3 _correctedLookAtPosition;
diff --git a/libraries/avatars-renderer/src/AvatarsRendererLogging.cpp b/libraries/avatars-renderer/src/avatars-renderer/Logging.cpp
similarity index 89%
rename from libraries/avatars-renderer/src/AvatarsRendererLogging.cpp
rename to libraries/avatars-renderer/src/avatars-renderer/Logging.cpp
index 2804df1b7a..f50902354d 100644
--- a/libraries/avatars-renderer/src/AvatarsRendererLogging.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Logging.cpp
@@ -6,6 +6,6 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-#include "AvatarsRendererLogging.h"
+#include "Logging.h"
Q_LOGGING_CATEGORY(avatars_renderer, "hifi.avatars.rendering")
diff --git a/libraries/avatars-renderer/src/AvatarsRendererLogging.h b/libraries/avatars-renderer/src/avatars-renderer/Logging.h
similarity index 100%
rename from libraries/avatars-renderer/src/AvatarsRendererLogging.h
rename to libraries/avatars-renderer/src/avatars-renderer/Logging.h
diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp
new file mode 100644
index 0000000000..ad69ba24cb
--- /dev/null
+++ b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp
@@ -0,0 +1,16 @@
+//
+// Created by Bradley Austin Davis on 2017/04/27
+// Copyright 2013-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 "OtherAvatar.h"
+
+OtherAvatar::OtherAvatar(QThread* thread, RigPointer rig) : Avatar(thread, rig) {
+ // give the pointer to our head to inherited _headData variable from AvatarData
+ _headData = new Head(this);
+ _skeletonModel = std::make_shared(this, nullptr, rig);
+ connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
+}
diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h
new file mode 100644
index 0000000000..2f6c9a38aa
--- /dev/null
+++ b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h
@@ -0,0 +1,20 @@
+//
+// Created by Bradley Austin Davis on 2017/04/27
+// Copyright 2013-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_OtherAvatar_h
+#define hifi_OtherAvatar_h
+
+#include "Avatar.h"
+
+class OtherAvatar : public Avatar {
+public:
+ explicit OtherAvatar(QThread* thread, RigPointer rig = nullptr);
+ void instantiableAvatar() {};
+};
+
+#endif // hifi_OtherAvatar_h
diff --git a/interface/src/avatar/ScriptAvatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.cpp
similarity index 100%
rename from interface/src/avatar/ScriptAvatar.cpp
rename to libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.cpp
diff --git a/interface/src/avatar/ScriptAvatar.h b/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h
similarity index 100%
rename from interface/src/avatar/ScriptAvatar.h
rename to libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h
diff --git a/interface/src/avatar/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
similarity index 66%
rename from interface/src/avatar/SkeletonModel.cpp
rename to libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
index f81a83523b..e1e5dc4282 100644
--- a/interface/src/avatar/SkeletonModel.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
@@ -9,19 +9,18 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "SkeletonModel.h"
+
#include
#include
#include
#include
+#include
+#include
-#include "Application.h"
#include "Avatar.h"
-#include "Menu.h"
-#include "SkeletonModel.h"
-#include "Util.h"
-#include "InterfaceLogging.h"
-#include "AnimDebugDraw.h"
+#include "Logging.h"
SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer rig) :
CauterizedModel(rig, parent),
@@ -47,7 +46,7 @@ void SkeletonModel::initJointStates() {
// Determine the default eye position for avatar scale = 1.0
int headJointIndex = geometry.headJointIndex;
if (0 > headJointIndex || headJointIndex >= _rig->getJointStateCount()) {
- qCWarning(interfaceapp) << "Bad head joint! Got:" << headJointIndex << "jointCount:" << _rig->getJointStateCount();
+ qCWarning(avatars_renderer) << "Bad head joint! Got:" << headJointIndex << "jointCount:" << _rig->getJointStateCount();
}
glm::vec3 leftEyePosition, rightEyePosition;
getEyeModelPositions(leftEyePosition, rightEyePosition);
@@ -72,21 +71,6 @@ void SkeletonModel::initJointStates() {
emit skeletonLoaded();
}
-Rig::CharacterControllerState convertCharacterControllerState(CharacterController::State state) {
- switch (state) {
- default:
- case CharacterController::State::Ground:
- return Rig::CharacterControllerState::Ground;
- case CharacterController::State::Takeoff:
- return Rig::CharacterControllerState::Takeoff;
- case CharacterController::State::InAir:
- return Rig::CharacterControllerState::InAir;
- case CharacterController::State::Hover:
- return Rig::CharacterControllerState::Hover;
- };
-}
-
-
// Called within Model::simulate call, below.
void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
const FBXGeometry& geometry = getFBXGeometry();
@@ -102,122 +86,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
lookAt = _owningAvatar->getHead()->getEyePosition() + (MIN_LOOK_AT_FOCUS_DISTANCE / focusDistance) * focusOffset;
}
- if (_owningAvatar->isMyAvatar()) {
- MyAvatar* myAvatar = static_cast(_owningAvatar);
-
- Rig::HeadParameters headParams;
-
- // input action is the highest priority source for head orientation.
- auto avatarHeadPose = myAvatar->getHeadControllerPoseInAvatarFrame();
- if (avatarHeadPose.isValid()) {
- glm::mat4 rigHeadMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation());
- headParams.rigHeadPosition = extractTranslation(rigHeadMat);
- headParams.rigHeadOrientation = glmExtractRotation(rigHeadMat);
- headParams.headEnabled = true;
- } else {
- if (qApp->isHMDMode()) {
- // get HMD position from sensor space into world space, and back into rig space
- glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();
- glm::mat4 rigToWorld = createMatFromQuatAndPos(getRotation(), getTranslation());
- glm::mat4 worldToRig = glm::inverse(rigToWorld);
- glm::mat4 rigHMDMat = worldToRig * worldHMDMat;
- _rig->computeHeadFromHMD(AnimPose(rigHMDMat), headParams.rigHeadPosition, headParams.rigHeadOrientation);
- headParams.headEnabled = true;
- } else {
- // even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and down in desktop mode.
- // preMult 180 is necessary to convert from avatar to rig coordinates.
- // postMult 180 is necessary to convert head from -z forward to z forward.
- headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180;
- headParams.headEnabled = false;
- }
- }
-
- auto avatarHipsPose = myAvatar->getHipsControllerPoseInAvatarFrame();
- if (avatarHipsPose.isValid()) {
- glm::mat4 rigHipsMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarHipsPose.getRotation(), avatarHipsPose.getTranslation());
- headParams.hipsMatrix = rigHipsMat;
- headParams.hipsEnabled = true;
- } else {
- headParams.hipsEnabled = false;
- }
-
- auto avatarSpine2Pose = myAvatar->getSpine2ControllerPoseInAvatarFrame();
- if (avatarSpine2Pose.isValid()) {
- glm::mat4 rigSpine2Mat = Matrices::Y_180 * createMatFromQuatAndPos(avatarSpine2Pose.getRotation(), avatarSpine2Pose.getTranslation());
- headParams.spine2Matrix = rigSpine2Mat;
- headParams.spine2Enabled = true;
- } else {
- headParams.spine2Enabled = false;
- }
-
- headParams.isTalking = head->getTimeWithoutTalking() <= 1.5f;
-
- _rig->updateFromHeadParameters(headParams, deltaTime);
-
- Rig::HandAndFeetParameters handAndFeetParams;
-
- auto leftPose = myAvatar->getLeftHandControllerPoseInAvatarFrame();
- if (leftPose.isValid()) {
- handAndFeetParams.isLeftEnabled = true;
- handAndFeetParams.leftPosition = Quaternions::Y_180 * leftPose.getTranslation();
- handAndFeetParams.leftOrientation = Quaternions::Y_180 * leftPose.getRotation();
- } else {
- handAndFeetParams.isLeftEnabled = false;
- }
-
- auto rightPose = myAvatar->getRightHandControllerPoseInAvatarFrame();
- if (rightPose.isValid()) {
- handAndFeetParams.isRightEnabled = true;
- handAndFeetParams.rightPosition = Quaternions::Y_180 * rightPose.getTranslation();
- handAndFeetParams.rightOrientation = Quaternions::Y_180 * rightPose.getRotation();
- } else {
- handAndFeetParams.isRightEnabled = false;
- }
-
- auto leftFootPose = myAvatar->getLeftFootControllerPoseInAvatarFrame();
- if (leftFootPose.isValid()) {
- handAndFeetParams.isLeftFootEnabled = true;
- handAndFeetParams.leftFootPosition = Quaternions::Y_180 * leftFootPose.getTranslation();
- handAndFeetParams.leftFootOrientation = Quaternions::Y_180 * leftFootPose.getRotation();
- } else {
- handAndFeetParams.isLeftFootEnabled = false;
- }
-
- auto rightFootPose = myAvatar->getRightFootControllerPoseInAvatarFrame();
- if (rightFootPose.isValid()) {
- handAndFeetParams.isRightFootEnabled = true;
- handAndFeetParams.rightFootPosition = Quaternions::Y_180 * rightFootPose.getTranslation();
- handAndFeetParams.rightFootOrientation = Quaternions::Y_180 * rightFootPose.getRotation();
- } else {
- handAndFeetParams.isRightFootEnabled = false;
- }
-
- handAndFeetParams.bodyCapsuleRadius = myAvatar->getCharacterController()->getCapsuleRadius();
- handAndFeetParams.bodyCapsuleHalfHeight = myAvatar->getCharacterController()->getCapsuleHalfHeight();
- handAndFeetParams.bodyCapsuleLocalOffset = myAvatar->getCharacterController()->getCapsuleLocalOffset();
-
- _rig->updateFromHandAndFeetParameters(handAndFeetParams, deltaTime);
-
- Rig::CharacterControllerState ccState = convertCharacterControllerState(myAvatar->getCharacterController()->getState());
-
- auto velocity = myAvatar->getLocalVelocity();
- auto position = myAvatar->getLocalPosition();
- auto orientation = myAvatar->getLocalOrientation();
- _rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState);
-
- // evaluate AnimGraph animation and update jointStates.
- Model::updateRig(deltaTime, parentTransform);
-
- Rig::EyeParameters eyeParams;
- eyeParams.eyeLookAt = lookAt;
- eyeParams.eyeSaccade = head->getSaccade();
- eyeParams.modelRotation = getRotation();
- eyeParams.modelTranslation = getTranslation();
- eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex;
- eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex;
-
- _rig->updateFromEyeParameters(eyeParams);
- } else {
+ if (!_owningAvatar->isMyAvatar()) {
// no need to call Model::updateRig() because otherAvatars get their joint state
// copied directly from AvtarData::_jointData (there are no Rig animations to blend)
_needsUpdateClusterMatrices = true;
@@ -249,6 +118,9 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
_rig->updateFromEyeParameters(eyeParams);
}
+
+ // evaluate AnimGraph animation and update jointStates.
+ Parent::updateRig(deltaTime, parentTransform);
}
void SkeletonModel::updateAttitude() {
@@ -264,7 +136,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
if (fullUpdate) {
setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients());
- Model::simulate(deltaTime, fullUpdate);
+ Parent::simulate(deltaTime, fullUpdate);
// let rig compute the model offset
glm::vec3 registrationPoint;
@@ -272,7 +144,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
setOffset(registrationPoint);
}
} else {
- Model::simulate(deltaTime, fullUpdate);
+ Parent::simulate(deltaTime, fullUpdate);
}
if (!isActive() || !_owningAvatar->isMyAvatar()) {
diff --git a/interface/src/avatar/SkeletonModel.h b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h
similarity index 99%
rename from interface/src/avatar/SkeletonModel.h
rename to libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h
index 7a6081a010..059dd245fd 100644
--- a/interface/src/avatar/SkeletonModel.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h
@@ -23,6 +23,7 @@ using SkeletonModelWeakPointer = std::weak_ptr;
/// A skeleton loaded from a model.
class SkeletonModel : public CauterizedModel {
+ using Parent = CauterizedModel;
Q_OBJECT
public:
@@ -114,7 +115,7 @@ protected:
void computeBoundingShape();
-private:
+protected:
bool getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index b4c79470de..6d801793b7 100644
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -94,7 +94,7 @@ const quint32 AVATAR_MOTION_SCRIPTABLE_BITS =
// +-----+-----+-+-+-+--+
// Key state - K0,K1 is found in the 1st and 2nd bits
// Hand state - H0,H1,H2 is found in the 3rd, 4th, and 8th bits
-// Faceshift - F is found in the 5th bit
+// Face tracker - F is found in the 5th bit
// Eye tracker - E is found in the 6th bit
// Referential Data - R is found in the 7th bit
const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits
@@ -123,7 +123,7 @@ namespace AvatarDataPacket {
// it might be nice to use a dictionary to compress that
// Packet State Flags - we store the details about the existence of other records in this bitset:
- // AvatarGlobalPosition, Avatar Faceshift, eye tracking, and existence of
+ // AvatarGlobalPosition, Avatar face tracker, eye tracking, and existence of
using HasFlags = uint16_t;
const HasFlags PACKET_HAS_AVATAR_GLOBAL_POSITION = 1U << 0;
const HasFlags PACKET_HAS_AVATAR_BOUNDING_BOX = 1U << 1;
@@ -357,6 +357,8 @@ class AvatarData : public QObject, public SpatiallyNestable {
public:
+ virtual QString getName() const override { return QString("Avatar:") + _displayName; }
+
static const QString FRAME_NAME;
static void fromFrame(const QByteArray& frameData, AvatarData& avatar, bool useFrameSkeleton = true);
diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp
index 271ce133a2..b55be7c156 100644
--- a/libraries/avatars/src/HeadData.cpp
+++ b/libraries/avatars/src/HeadData.cpp
@@ -23,11 +23,6 @@
#include "AvatarData.h"
-/// The names of the blendshapes expected by Faceshift, terminated with an empty string.
-extern const char* FACESHIFT_BLENDSHAPES[];
-/// The size of FACESHIFT_BLENDSHAPES
-extern const int NUM_FACESHIFT_BLENDSHAPES;
-
HeadData::HeadData(AvatarData* owningAvatar) :
_baseYaw(0.0f),
_basePitch(0.0f),
diff --git a/libraries/entities/src/EntityDynamicInterface.cpp b/libraries/entities/src/EntityDynamicInterface.cpp
index 2ab9a60397..57f86105b2 100644
--- a/libraries/entities/src/EntityDynamicInterface.cpp
+++ b/libraries/entities/src/EntityDynamicInterface.cpp
@@ -117,6 +117,15 @@ EntityDynamicType EntityDynamicInterface::dynamicTypeFromString(QString dynamicT
if (normalizedDynamicTypeString == "fargrab") {
return DYNAMIC_TYPE_FAR_GRAB;
}
+ if (normalizedDynamicTypeString == "slider") {
+ return DYNAMIC_TYPE_SLIDER;
+ }
+ if (normalizedDynamicTypeString == "ballsocket") {
+ return DYNAMIC_TYPE_BALL_SOCKET;
+ }
+ if (normalizedDynamicTypeString == "conetwist") {
+ return DYNAMIC_TYPE_CONE_TWIST;
+ }
qCDebug(entities) << "Warning -- EntityDynamicInterface::dynamicTypeFromString got unknown dynamic-type name"
<< dynamicTypeString;
@@ -139,6 +148,12 @@ QString EntityDynamicInterface::dynamicTypeToString(EntityDynamicType dynamicTyp
return "hinge";
case DYNAMIC_TYPE_FAR_GRAB:
return "far-grab";
+ case DYNAMIC_TYPE_SLIDER:
+ return "slider";
+ case DYNAMIC_TYPE_BALL_SOCKET:
+ return "ball-socket";
+ case DYNAMIC_TYPE_CONE_TWIST:
+ return "cone-twist";
}
assert(false);
return "none";
diff --git a/libraries/entities/src/EntityDynamicInterface.h b/libraries/entities/src/EntityDynamicInterface.h
index 93d9ffa43e..c04aaf67b2 100644
--- a/libraries/entities/src/EntityDynamicInterface.h
+++ b/libraries/entities/src/EntityDynamicInterface.h
@@ -31,7 +31,10 @@ enum EntityDynamicType {
DYNAMIC_TYPE_HOLD = 3000,
DYNAMIC_TYPE_TRAVEL_ORIENTED = 4000,
DYNAMIC_TYPE_HINGE = 5000,
- DYNAMIC_TYPE_FAR_GRAB = 6000
+ DYNAMIC_TYPE_FAR_GRAB = 6000,
+ DYNAMIC_TYPE_SLIDER = 7000,
+ DYNAMIC_TYPE_BALL_SOCKET = 8000,
+ DYNAMIC_TYPE_CONE_TWIST = 9000
};
diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h
index ff5f12b2f7..1896893b52 100644
--- a/libraries/entities/src/EntityItem.h
+++ b/libraries/entities/src/EntityItem.h
@@ -281,7 +281,7 @@ public:
float getAngularDamping() const;
void setAngularDamping(float value);
- QString getName() const;
+ virtual QString getName() const override;
void setName(const QString& value);
QString getDebugName();
diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp
index 10479e931c..ffb65a2dba 100644
--- a/libraries/entities/src/EntityScriptingInterface.cpp
+++ b/libraries/entities/src/EntityScriptingInterface.cpp
@@ -407,9 +407,11 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
// return QUuid();
// }
+ bool entityFound { false };
_entityTree->withReadLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (entity) {
+ entityFound = true;
// make sure the properties has a type, so that the encode can know which properties to include
properties.setType(entity->getType());
bool hasTerseUpdateChanges = properties.hasTerseUpdateChanges();
@@ -464,6 +466,27 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
});
}
});
+ if (!entityFound) {
+ // we've made an edit to an entity we don't know about, or to a non-entity. If it's a known non-entity,
+ // print a warning and don't send an edit packet to the entity-server.
+ QSharedPointer parentFinder = DependencyManager::get();
+ if (parentFinder) {
+ bool success;
+ auto nestableWP = parentFinder->find(id, success, static_cast(_entityTree.get()));
+ if (success) {
+ auto nestable = nestableWP.lock();
+ if (nestable) {
+ NestableType nestableType = nestable->getNestableType();
+ if (nestableType == NestableType::Overlay || nestableType == NestableType::Avatar) {
+ qCWarning(entities) << "attempted edit on non-entity: " << id << nestable->getName();
+ return QUuid(); // null UUID to indicate failure
+ }
+ }
+ }
+ }
+ }
+ // we queue edit packets even if we don't know about the entity. This is to allow AC agents
+ // to edit entities they know only by ID.
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
return id;
}
@@ -1515,6 +1538,24 @@ bool EntityScriptingInterface::isChildOfParent(QUuid childID, QUuid parentID) {
return isChild;
}
+QString EntityScriptingInterface::getNestableType(QUuid id) {
+ QSharedPointer parentFinder = DependencyManager::get();
+ if (!parentFinder) {
+ return "unknown";
+ }
+ bool success;
+ SpatiallyNestableWeakPointer objectWP = parentFinder->find(id, success);
+ if (!success) {
+ return "unknown";
+ }
+ SpatiallyNestablePointer object = objectWP.lock();
+ if (!object) {
+ return "unknown";
+ }
+ NestableType nestableType = object->getNestableType();
+ return SpatiallyNestable::nestableTypeToString(nestableType);
+}
+
QVector EntityScriptingInterface::getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex) {
QVector result;
if (!_entityTree) {
diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h
index b25764790e..f5656860e3 100644
--- a/libraries/entities/src/EntityScriptingInterface.h
+++ b/libraries/entities/src/EntityScriptingInterface.h
@@ -304,6 +304,8 @@ public slots:
Q_INVOKABLE QVector getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex);
Q_INVOKABLE bool isChildOfParent(QUuid childID, QUuid parentID);
+ Q_INVOKABLE QString getNestableType(QUuid id);
+
Q_INVOKABLE QUuid getKeyboardFocusEntity() const;
Q_INVOKABLE void setKeyboardFocusEntity(QUuid id);
diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp
index 3ad5cc92a5..76483d0786 100644
--- a/libraries/entities/src/EntityTree.cpp
+++ b/libraries/entities/src/EntityTree.cpp
@@ -990,6 +990,17 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
entityItemID, properties);
endDecode = usecTimestampNow();
+ EntityItemPointer existingEntity;
+ if (!isAdd) {
+ // search for the entity by EntityItemID
+ startLookup = usecTimestampNow();
+ existingEntity = findEntityByEntityItemID(entityItemID);
+ endLookup = usecTimestampNow();
+ if (!existingEntity) {
+ // this is not an add-entity operation, and we don't know about the identified entity.
+ validEditPacket = false;
+ }
+ }
if (validEditPacket && !_entityScriptSourceWhitelist.isEmpty() && !properties.getScript().isEmpty()) {
bool passedWhiteList = false;
@@ -1036,12 +1047,6 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
// If we got a valid edit packet, then it could be a new entity or it could be an update to
// an existing entity... handle appropriately
if (validEditPacket) {
-
- // search for the entity by EntityItemID
- startLookup = usecTimestampNow();
- EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID);
- endLookup = usecTimestampNow();
-
startFilter = usecTimestampNow();
bool wasChanged = false;
// Having (un)lock rights bypasses the filter, unless it's a physics result.
diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp
index 7b46556530..167cb8caac 100644
--- a/libraries/fbx/src/OBJReader.cpp
+++ b/libraries/fbx/src/OBJReader.cpp
@@ -24,6 +24,7 @@
#include
#include
+#include
#include "FBXReader.h"
#include "ModelFormatLogging.h"
@@ -165,6 +166,7 @@ bool OBJFace::add(const QByteArray& vertexIndex, const QByteArray& textureIndex,
}
return true;
}
+
QVector OBJFace::triangulate() {
QVector newFaces;
const int nVerticesInATriangle = 3;
@@ -183,6 +185,7 @@ QVector OBJFace::triangulate() {
}
return newFaces;
}
+
void OBJFace::addFrom(const OBJFace* face, int index) { // add using data from f at index i
vertexIndices.append(face->vertexIndices[index]);
if (face->textureUVIndices.count() > 0) { // Any at all. Runtime error if not consistent.
@@ -193,24 +196,13 @@ void OBJFace::addFrom(const OBJFace* face, int index) { // add using data from f
}
}
-static bool replyOK(QNetworkReply* netReply, QUrl url) { // This will be reworked when we make things asynchronous
- return (netReply && netReply->isFinished() &&
- (url.toString().startsWith("file", Qt::CaseInsensitive) ? // file urls don't have http status codes
- netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString().isEmpty() :
- (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200)));
-}
-
bool OBJReader::isValidTexture(const QByteArray &filename) {
if (_url.isEmpty()) {
return false;
}
QUrl candidateUrl = _url.resolved(QUrl(filename));
- QNetworkReply *netReply = request(candidateUrl, true);
- bool isValid = replyOK(netReply, candidateUrl);
- if (netReply) {
- netReply->deleteLater();
- }
- return isValid;
+
+ return ResourceManager::resourceExists(candidateUrl);
}
void OBJReader::parseMaterialLibrary(QIODevice* device) {
@@ -274,7 +266,28 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) {
}
}
-QNetworkReply* OBJReader::request(QUrl& url, bool isTest) {
+std::tuple requestData(QUrl& url) {
+ auto request = ResourceManager::createResourceRequest(nullptr, url);
+
+ if (!request) {
+ return std::make_tuple(false, QByteArray());
+ }
+
+ request->send();
+
+ QEventLoop loop;
+ QObject::connect(request, &ResourceRequest::finished, &loop, &QEventLoop::quit);
+ loop.exec();
+
+ if (request->getResult() == ResourceRequest::Success) {
+ return std::make_tuple(true, request->getData());
+ } else {
+ return std::make_tuple(false, QByteArray());
+ }
+}
+
+
+QNetworkReply* request(QUrl& url, bool isTest) {
if (!qApp) {
return nullptr;
}
@@ -293,10 +306,7 @@ QNetworkReply* OBJReader::request(QUrl& url, bool isTest) {
QEventLoop loop; // Create an event loop that will quit when we get the finished signal
QObject::connect(netReply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec(); // Nothing is going to happen on this whole run thread until we get this
- static const int WAIT_TIMEOUT_MS = 500;
- while (!aboutToQuit && qApp && !netReply->isReadable()) {
- netReply->waitForReadyRead(WAIT_TIMEOUT_MS); // so we might as well block this thread waiting for the response, rather than
- }
+
QObject::disconnect(connection);
return netReply; // trying to sync later on.
}
@@ -446,142 +456,142 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping,
// add a new meshPart to the geometry's single mesh.
while (parseOBJGroup(tokenizer, mapping, geometry, scaleGuess, combineParts)) {}
- FBXMesh& mesh = geometry.meshes[0];
- mesh.meshIndex = 0;
+ FBXMesh& mesh = geometry.meshes[0];
+ mesh.meshIndex = 0;
- geometry.joints.resize(1);
- geometry.joints[0].isFree = false;
- geometry.joints[0].parentIndex = -1;
- geometry.joints[0].distanceToParent = 0;
- geometry.joints[0].translation = glm::vec3(0, 0, 0);
- geometry.joints[0].rotationMin = glm::vec3(0, 0, 0);
- geometry.joints[0].rotationMax = glm::vec3(0, 0, 0);
- geometry.joints[0].name = "OBJ";
- geometry.joints[0].isSkeletonJoint = true;
+ geometry.joints.resize(1);
+ geometry.joints[0].isFree = false;
+ geometry.joints[0].parentIndex = -1;
+ geometry.joints[0].distanceToParent = 0;
+ geometry.joints[0].translation = glm::vec3(0, 0, 0);
+ geometry.joints[0].rotationMin = glm::vec3(0, 0, 0);
+ geometry.joints[0].rotationMax = glm::vec3(0, 0, 0);
+ geometry.joints[0].name = "OBJ";
+ geometry.joints[0].isSkeletonJoint = true;
- geometry.jointIndices["x"] = 1;
+ geometry.jointIndices["x"] = 1;
- FBXCluster cluster;
- cluster.jointIndex = 0;
- cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0,
- 0, 1, 0, 0,
- 0, 0, 1, 0,
- 0, 0, 0, 1);
- mesh.clusters.append(cluster);
+ FBXCluster cluster;
+ cluster.jointIndex = 0;
+ cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1);
+ mesh.clusters.append(cluster);
- QMap materialMeshIdMap;
- QVector fbxMeshParts;
- for (int i = 0, meshPartCount = 0; i < mesh.parts.count(); i++, meshPartCount++) {
- FBXMeshPart& meshPart = mesh.parts[i];
- FaceGroup faceGroup = faceGroups[meshPartCount];
+ QMap materialMeshIdMap;
+ QVector fbxMeshParts;
+ for (int i = 0, meshPartCount = 0; i < mesh.parts.count(); i++, meshPartCount++) {
+ FBXMeshPart& meshPart = mesh.parts[i];
+ FaceGroup faceGroup = faceGroups[meshPartCount];
bool specifiesUV = false;
- foreach(OBJFace face, faceGroup) {
- // Go through all of the OBJ faces and determine the number of different materials necessary (each different material will be a unique mesh).
- // NOTE (trent/mittens 3/30/17): this seems hardcore wasteful and is slowed down a bit by iterating through the face group twice, but it's the best way I've thought of to hack multi-material support in an OBJ into this pipeline.
- if (!materialMeshIdMap.contains(face.materialName)) {
- // Create a new FBXMesh for this material mapping.
- materialMeshIdMap.insert(face.materialName, materialMeshIdMap.count());
+ foreach(OBJFace face, faceGroup) {
+ // Go through all of the OBJ faces and determine the number of different materials necessary (each different material will be a unique mesh).
+ // NOTE (trent/mittens 3/30/17): this seems hardcore wasteful and is slowed down a bit by iterating through the face group twice, but it's the best way I've thought of to hack multi-material support in an OBJ into this pipeline.
+ if (!materialMeshIdMap.contains(face.materialName)) {
+ // Create a new FBXMesh for this material mapping.
+ materialMeshIdMap.insert(face.materialName, materialMeshIdMap.count());
- fbxMeshParts.append(FBXMeshPart());
- FBXMeshPart& meshPartNew = fbxMeshParts.last();
- meshPartNew.quadIndices = QVector(meshPart.quadIndices); // Copy over quad indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway].
- meshPartNew.quadTrianglesIndices = QVector(meshPart.quadTrianglesIndices); // Copy over quad triangulated indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway].
- meshPartNew.triangleIndices = QVector(meshPart.triangleIndices); // Copy over triangle indices.
+ fbxMeshParts.append(FBXMeshPart());
+ FBXMeshPart& meshPartNew = fbxMeshParts.last();
+ meshPartNew.quadIndices = QVector(meshPart.quadIndices); // Copy over quad indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway].
+ meshPartNew.quadTrianglesIndices = QVector(meshPart.quadTrianglesIndices); // Copy over quad triangulated indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway].
+ meshPartNew.triangleIndices = QVector(meshPart.triangleIndices); // Copy over triangle indices.
- // Do some of the material logic (which previously lived below) now.
- // All the faces in the same group will have the same name and material.
- QString groupMaterialName = face.materialName;
- if (groupMaterialName.isEmpty() && specifiesUV) {
+ // Do some of the material logic (which previously lived below) now.
+ // All the faces in the same group will have the same name and material.
+ QString groupMaterialName = face.materialName;
+ if (groupMaterialName.isEmpty() && specifiesUV) {
#ifdef WANT_DEBUG
- qCDebug(modelformat) << "OBJ Reader WARNING: " << url
- << " needs a texture that isn't specified. Using default mechanism.";
+ qCDebug(modelformat) << "OBJ Reader WARNING: " << url
+ << " needs a texture that isn't specified. Using default mechanism.";
#endif
- groupMaterialName = SMART_DEFAULT_MATERIAL_NAME;
- }
- if (!groupMaterialName.isEmpty()) {
- OBJMaterial& material = materials[groupMaterialName];
- if (specifiesUV) {
- material.userSpecifiesUV = true; // Note might not be true in a later usage.
- }
- if (specifiesUV || (groupMaterialName.compare("none", Qt::CaseInsensitive) != 0)) {
- // Blender has a convention that a material named "None" isn't really used (or defined).
- material.used = true;
- needsMaterialLibrary = groupMaterialName != SMART_DEFAULT_MATERIAL_NAME;
- }
- materials[groupMaterialName] = material;
- meshPartNew.materialID = groupMaterialName;
- }
- }
- }
- }
-
- // clean up old mesh parts.
- int unmodifiedMeshPartCount = mesh.parts.count();
- mesh.parts.clear();
- mesh.parts = QVector(fbxMeshParts);
-
- for (int i = 0, meshPartCount = 0; i < unmodifiedMeshPartCount; i++, meshPartCount++) {
- FaceGroup faceGroup = faceGroups[meshPartCount];
-
- // Now that each mesh has been created with its own unique material mappings, fill them with data (vertex data is duplicated, face data is not).
- foreach(OBJFace face, faceGroup) {
- FBXMeshPart& meshPart = mesh.parts[materialMeshIdMap[face.materialName]];
-
- glm::vec3 v0 = checked_at(vertices, face.vertexIndices[0]);
- glm::vec3 v1 = checked_at(vertices, face.vertexIndices[1]);
- glm::vec3 v2 = checked_at(vertices, face.vertexIndices[2]);
-
- // Scale the vertices if the OBJ file scale is specified as non-one.
- if (scaleGuess != 1.0f) {
- v0 *= scaleGuess;
- v1 *= scaleGuess;
- v2 *= scaleGuess;
- }
-
- // Add the vertices.
- meshPart.triangleIndices.append(mesh.vertices.count()); // not face.vertexIndices into vertices
- mesh.vertices << v0;
- meshPart.triangleIndices.append(mesh.vertices.count());
- mesh.vertices << v1;
- meshPart.triangleIndices.append(mesh.vertices.count());
- mesh.vertices << v2;
-
- glm::vec3 n0, n1, n2;
- if (face.normalIndices.count()) {
- n0 = checked_at(normals, face.normalIndices[0]);
- n1 = checked_at(normals, face.normalIndices[1]);
- n2 = checked_at(normals, face.normalIndices[2]);
- } else {
- // generate normals from triangle plane if not provided
- n0 = n1 = n2 = glm::cross(v1 - v0, v2 - v0);
- }
-
- mesh.normals.append(n0);
- mesh.normals.append(n1);
- mesh.normals.append(n2);
-
- if (face.textureUVIndices.count()) {
- mesh.texCoords <<
- checked_at(textureUVs, face.textureUVIndices[0]) <<
- checked_at(textureUVs, face.textureUVIndices[1]) <<
- checked_at(textureUVs, face.textureUVIndices[2]);
- } else {
- glm::vec2 corner(0.0f, 1.0f);
- mesh.texCoords << corner << corner << corner;
- }
- }
+ groupMaterialName = SMART_DEFAULT_MATERIAL_NAME;
+ }
+ if (!groupMaterialName.isEmpty()) {
+ OBJMaterial& material = materials[groupMaterialName];
+ if (specifiesUV) {
+ material.userSpecifiesUV = true; // Note might not be true in a later usage.
+ }
+ if (specifiesUV || (groupMaterialName.compare("none", Qt::CaseInsensitive) != 0)) {
+ // Blender has a convention that a material named "None" isn't really used (or defined).
+ material.used = true;
+ needsMaterialLibrary = groupMaterialName != SMART_DEFAULT_MATERIAL_NAME;
+ }
+ materials[groupMaterialName] = material;
+ meshPartNew.materialID = groupMaterialName;
+ }
+ }
+ }
}
- mesh.meshExtents.reset();
- foreach(const glm::vec3& vertex, mesh.vertices) {
- mesh.meshExtents.addPoint(vertex);
- geometry.meshExtents.addPoint(vertex);
- }
+ // clean up old mesh parts.
+ int unmodifiedMeshPartCount = mesh.parts.count();
+ mesh.parts.clear();
+ mesh.parts = QVector(fbxMeshParts);
- // Build the single mesh.
- FBXReader::buildModelMesh(mesh, url.toString());
+ for (int i = 0, meshPartCount = 0; i < unmodifiedMeshPartCount; i++, meshPartCount++) {
+ FaceGroup faceGroup = faceGroups[meshPartCount];
- // fbxDebugDump(geometry);
+ // Now that each mesh has been created with its own unique material mappings, fill them with data (vertex data is duplicated, face data is not).
+ foreach(OBJFace face, faceGroup) {
+ FBXMeshPart& meshPart = mesh.parts[materialMeshIdMap[face.materialName]];
+
+ glm::vec3 v0 = checked_at(vertices, face.vertexIndices[0]);
+ glm::vec3 v1 = checked_at(vertices, face.vertexIndices[1]);
+ glm::vec3 v2 = checked_at(vertices, face.vertexIndices[2]);
+
+ // Scale the vertices if the OBJ file scale is specified as non-one.
+ if (scaleGuess != 1.0f) {
+ v0 *= scaleGuess;
+ v1 *= scaleGuess;
+ v2 *= scaleGuess;
+ }
+
+ // Add the vertices.
+ meshPart.triangleIndices.append(mesh.vertices.count()); // not face.vertexIndices into vertices
+ mesh.vertices << v0;
+ meshPart.triangleIndices.append(mesh.vertices.count());
+ mesh.vertices << v1;
+ meshPart.triangleIndices.append(mesh.vertices.count());
+ mesh.vertices << v2;
+
+ glm::vec3 n0, n1, n2;
+ if (face.normalIndices.count()) {
+ n0 = checked_at(normals, face.normalIndices[0]);
+ n1 = checked_at(normals, face.normalIndices[1]);
+ n2 = checked_at(normals, face.normalIndices[2]);
+ } else {
+ // generate normals from triangle plane if not provided
+ n0 = n1 = n2 = glm::cross(v1 - v0, v2 - v0);
+ }
+
+ mesh.normals.append(n0);
+ mesh.normals.append(n1);
+ mesh.normals.append(n2);
+
+ if (face.textureUVIndices.count()) {
+ mesh.texCoords <<
+ checked_at(textureUVs, face.textureUVIndices[0]) <<
+ checked_at(textureUVs, face.textureUVIndices[1]) <<
+ checked_at(textureUVs, face.textureUVIndices[2]);
+ } else {
+ glm::vec2 corner(0.0f, 1.0f);
+ mesh.texCoords << corner << corner << corner;
+ }
+ }
+ }
+
+ mesh.meshExtents.reset();
+ foreach(const glm::vec3& vertex, mesh.vertices) {
+ mesh.meshExtents.addPoint(vertex);
+ geometry.meshExtents.addPoint(vertex);
+ }
+
+ // Build the single mesh.
+ FBXReader::buildModelMesh(mesh, url.toString());
+
+ // fbxDebugDump(geometry);
} catch(const std::exception& e) {
qCDebug(modelformat) << "OBJ reader fail: " << e.what();
}
@@ -624,15 +634,15 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping,
// Throw away any path part of libraryName, and merge against original url.
QUrl libraryUrl = _url.resolved(QUrl(libraryName).fileName());
qCDebug(modelformat) << "OBJ Reader material library" << libraryName << "used in" << _url;
- QNetworkReply* netReply = request(libraryUrl, false);
- if (replyOK(netReply, libraryUrl)) {
- parseMaterialLibrary(netReply);
+ bool success;
+ QByteArray data;
+ std::tie(success, data) = requestData(libraryUrl);
+ if (success) {
+ QBuffer buffer { &data };
+ buffer.open(QIODevice::ReadOnly);
+ parseMaterialLibrary(&buffer);
} else {
- qCDebug(modelformat) << "OBJ Reader WARNING:" << libraryName << "did not answer. Got"
- << (!netReply ? "aborted" : netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString());
- }
- if (netReply) {
- netReply->deleteLater();
+ qCDebug(modelformat) << "OBJ Reader WARNING:" << libraryName << "did not answer";
}
}
}
@@ -655,9 +665,9 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping,
if (!objMaterial.diffuseTextureFilename.isEmpty()) {
fbxMaterial.albedoTexture.filename = objMaterial.diffuseTextureFilename;
}
- if (!objMaterial.specularTextureFilename.isEmpty()) {
- fbxMaterial.specularTexture.filename = objMaterial.specularTextureFilename;
- }
+ if (!objMaterial.specularTextureFilename.isEmpty()) {
+ fbxMaterial.specularTexture.filename = objMaterial.specularTextureFilename;
+ }
modelMaterial->setEmissive(fbxMaterial.emissiveColor);
modelMaterial->setAlbedo(fbxMaterial.diffuseColor);
diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h
index 4be5705f9a..18a4b89f1e 100644
--- a/libraries/fbx/src/OBJReader.h
+++ b/libraries/fbx/src/OBJReader.h
@@ -72,7 +72,6 @@ public:
QString currentMaterialName;
QHash materials;
- QNetworkReply* request(QUrl& url, bool isTest);
FBXGeometry* readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl());
private:
diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp
index 7f724cce65..12bfb8e70b 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp
@@ -140,6 +140,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
switch (dstFormat.getSemantic()) {
case gpu::RGB:
case gpu::RGBA:
+ case gpu::XY:
result = GL_RG8;
break;
default:
@@ -289,6 +290,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
switch (dstFormat.getSemantic()) {
case gpu::RGB:
case gpu::RGBA:
+ case gpu::XY:
texel.internalFormat = GL_RG8;
break;
default:
@@ -516,6 +518,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
switch (dstFormat.getSemantic()) {
case gpu::RGB:
case gpu::RGBA:
+ case gpu::XY:
texel.internalFormat = GL_RG8;
break;
default:
diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
index c2cef5a21e..146554952e 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
@@ -525,11 +525,14 @@ void GL41VariableAllocationTexture::populateTransferQueue() {
// break down the transfers into chunks so that no single transfer is
// consuming more than X bandwidth
+ // For compressed format, regions must be a multiple of the 4x4 tiles, so enforce 4 lines as the minimum block
auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face);
const auto lines = mipDimensions.y;
- auto bytesPerLine = mipSize / lines;
+ const uint32_t BLOCK_NUM_LINES { 4 };
+ const auto numBlocks = (lines + (BLOCK_NUM_LINES - 1)) / BLOCK_NUM_LINES;
+ auto bytesPerBlock = mipSize / numBlocks;
Q_ASSERT(0 == (mipSize % lines));
- uint32_t linesPerTransfer = (uint32_t)(MAX_TRANSFER_SIZE / bytesPerLine);
+ uint32_t linesPerTransfer = BLOCK_NUM_LINES * (uint32_t)(MAX_TRANSFER_SIZE / bytesPerBlock);
uint32_t lineOffset = 0;
while (lineOffset < lines) {
uint32_t linesToCopy = std::min(lines - lineOffset, linesPerTransfer);
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index 4f40f47085..59e8c59d58 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -196,11 +196,14 @@ void GL45ResourceTexture::populateTransferQueue() {
// break down the transfers into chunks so that no single transfer is
// consuming more than X bandwidth
+ // For compressed format, regions must be a multiple of the 4x4 tiles, so enforce 4 lines as the minimum block
auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face);
const auto lines = mipDimensions.y;
- auto bytesPerLine = mipSize / lines;
+ const uint32_t BLOCK_NUM_LINES { 4 };
+ const auto numBlocks = (lines + (BLOCK_NUM_LINES - 1)) / BLOCK_NUM_LINES;
+ auto bytesPerBlock = mipSize / numBlocks;
Q_ASSERT(0 == (mipSize % lines));
- uint32_t linesPerTransfer = (uint32_t)(MAX_TRANSFER_SIZE / bytesPerLine);
+ uint32_t linesPerTransfer = BLOCK_NUM_LINES * (uint32_t)(MAX_TRANSFER_SIZE / bytesPerBlock);
uint32_t lineOffset = 0;
while (lineOffset < lines) {
uint32_t linesToCopy = std::min(lines - lineOffset, linesPerTransfer);
diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp
index 19d8855bd9..43bcd35b88 100644
--- a/libraries/gpu/src/gpu/Format.cpp
+++ b/libraries/gpu/src/gpu/Format.cpp
@@ -25,6 +25,8 @@ const Element Element::COLOR_COMPRESSED_SRGBA_MASK{ VEC4, NUINT8, COMPRESSED_BC1
const Element Element::COLOR_COMPRESSED_SRGBA{ VEC4, NUINT8, COMPRESSED_BC3_SRGBA };
const Element Element::COLOR_COMPRESSED_XY{ VEC4, NUINT8, COMPRESSED_BC5_XY };
+const Element Element::VEC2NU8_XY{ VEC2, NUINT8, XY };
+
const Element Element::COLOR_R11G11B10{ SCALAR, FLOAT, R11G11B10 };
const Element Element::VEC4F_COLOR_RGBA{ VEC4, FLOAT, RGBA };
const Element Element::VEC2F_UV{ VEC2, FLOAT, UV };
diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h
index f69e8d9386..8b7bbdcbed 100644
--- a/libraries/gpu/src/gpu/Format.h
+++ b/libraries/gpu/src/gpu/Format.h
@@ -234,6 +234,7 @@ public:
static const Element COLOR_COMPRESSED_SRGBA_MASK;
static const Element COLOR_COMPRESSED_SRGBA;
static const Element COLOR_COMPRESSED_XY;
+ static const Element VEC2NU8_XY;
static const Element VEC4F_COLOR_RGBA;
static const Element VEC2F_UV;
static const Element VEC2F_XY;
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index d2f93c0036..3fc4e0d432 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -99,6 +99,61 @@ struct GPUKTXPayload {
};
const std::string GPUKTXPayload::KEY { "hifi.gpu" };
+
+struct IrradianceKTXPayload {
+ using Version = uint8;
+
+ static const std::string KEY;
+ static const Version CURRENT_VERSION{ 0 };
+ static const size_t PADDING{ 3 };
+ static const size_t SIZE{ sizeof(Version) + sizeof(SphericalHarmonics) + PADDING };
+ static_assert(IrradianceKTXPayload::SIZE == 148, "Packing size may differ between platforms");
+ static_assert(IrradianceKTXPayload::SIZE % 4 == 0, "IrradianceKTXPayload is not 4 bytes aligned");
+
+ SphericalHarmonics _irradianceSH;
+
+ Byte* serialize(Byte* data) const {
+ *(Version*)data = CURRENT_VERSION;
+ data += sizeof(Version);
+
+ memcpy(data, &_irradianceSH, sizeof(SphericalHarmonics));
+ data += sizeof(SphericalHarmonics);
+
+ return data + PADDING;
+ }
+
+ bool unserialize(const Byte* data, size_t size) {
+ if (size != SIZE) {
+ return false;
+ }
+
+ Version version = *(const Version*)data;
+ if (version != CURRENT_VERSION) {
+ return false;
+ }
+ data += sizeof(Version);
+
+ memcpy(&_irradianceSH, data, sizeof(SphericalHarmonics));
+ data += sizeof(SphericalHarmonics);
+
+ return true;
+ }
+
+ static bool isIrradianceKTX(const ktx::KeyValue& val) {
+ return (val._key.compare(KEY) == 0);
+ }
+
+ static bool findInKeyValues(const ktx::KeyValues& keyValues, IrradianceKTXPayload& payload) {
+ auto found = std::find_if(keyValues.begin(), keyValues.end(), isIrradianceKTX);
+ if (found != keyValues.end()) {
+ auto value = found->_value;
+ return payload.unserialize(value.data(), value.size());
+ }
+ return false;
+ }
+};
+const std::string IrradianceKTXPayload::KEY{ "hifi.irradianceSH" };
+
KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
{
// We are doing a lot of work here just to get descriptor data
@@ -304,16 +359,27 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
}
}
- GPUKTXPayload keyval;
- keyval._samplerDesc = texture.getSampler().getDesc();
- keyval._usage = texture.getUsage();
- keyval._usageType = texture.getUsageType();
+ GPUKTXPayload gpuKeyval;
+ gpuKeyval._samplerDesc = texture.getSampler().getDesc();
+ gpuKeyval._usage = texture.getUsage();
+ gpuKeyval._usageType = texture.getUsageType();
+
Byte keyvalPayload[GPUKTXPayload::SIZE];
- keyval.serialize(keyvalPayload);
+ gpuKeyval.serialize(keyvalPayload);
ktx::KeyValues keyValues;
keyValues.emplace_back(GPUKTXPayload::KEY, (uint32)GPUKTXPayload::SIZE, (ktx::Byte*) &keyvalPayload);
+ if (texture.getIrradiance()) {
+ IrradianceKTXPayload irradianceKeyval;
+ irradianceKeyval._irradianceSH = *texture.getIrradiance();
+
+ Byte irradianceKeyvalPayload[IrradianceKTXPayload::SIZE];
+ irradianceKeyval.serialize(irradianceKeyvalPayload);
+
+ keyValues.emplace_back(IrradianceKTXPayload::KEY, (uint32)IrradianceKTXPayload::SIZE, (ktx::Byte*) &irradianceKeyvalPayload);
+ }
+
auto hash = texture.sourceHash();
if (!hash.empty()) {
// the sourceHash is an std::string in hex
@@ -409,6 +475,12 @@ TexturePointer Texture::unserialize(const std::string& ktxfile, const ktx::KTXDe
// Assing the mips availables
texture->setStoredMipFormat(mipFormat);
texture->setKtxBacking(ktxfile);
+
+ IrradianceKTXPayload irradianceKtxKeyValue;
+ if (IrradianceKTXPayload::findInKeyValues(descriptor.keyValues, irradianceKtxKeyValue)) {
+ texture->overrideIrradiance(std::make_shared(irradianceKtxKeyValue._irradianceSH));
+ }
+
return texture;
}
@@ -423,6 +495,8 @@ bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat
header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 1, ktx::GLFormat::RGBA, ktx::GLInternalFormat_Uncompressed::SRGB8_ALPHA8, ktx::GLBaseInternalFormat::RGBA);
} else if (texelFormat == Format::COLOR_R_8 && mipFormat == Format::COLOR_R_8) {
header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 1, ktx::GLFormat::RED, ktx::GLInternalFormat_Uncompressed::R8, ktx::GLBaseInternalFormat::RED);
+ } else if (texelFormat == Format::VEC2NU8_XY && mipFormat == Format::VEC2NU8_XY) {
+ header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 1, ktx::GLFormat::RG, ktx::GLInternalFormat_Uncompressed::RG8, ktx::GLBaseInternalFormat::RG);
} else if (texelFormat == Format::COLOR_COMPRESSED_SRGB && mipFormat == Format::COLOR_COMPRESSED_SRGB) {
header.setCompressed(ktx::GLInternalFormat_Compressed::COMPRESSED_SRGB_S3TC_DXT1_EXT, ktx::GLBaseInternalFormat::RGB);
} else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA_MASK && mipFormat == Format::COLOR_COMPRESSED_SRGBA_MASK) {
diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp
index 707a2e4496..68add428c1 100644
--- a/libraries/image/src/image/Image.cpp
+++ b/libraries/image/src/image/Image.cpp
@@ -22,16 +22,19 @@
#include
#include
#include
+#include
#include "ImageLogging.h"
using namespace gpu;
#define CPU_MIPMAPS 1
-#define COMPRESS_COLOR_TEXTURES 0
-#define COMPRESS_NORMALMAP_TEXTURES 0 // Disable Normalmap compression for now
-#define COMPRESS_GRAYSCALE_TEXTURES 0
-#define COMPRESS_CUBEMAP_TEXTURES 0 // Disable Cubemap compression for now
+
+static std::mutex settingsMutex;
+static Setting::Handle compressColorTextures("hifi.graphics.compressColorTextures", false);
+static Setting::Handle compressNormalTextures("hifi.graphics.compressNormalTextures", false);
+static Setting::Handle compressGrayscaleTextures("hifi.graphics.compressGrayscaleTextures", false);
+static Setting::Handle compressCubeTextures("hifi.graphics.compressCubeTextures", false);
static const glm::uvec2 SPARSE_PAGE_SIZE(128);
static const glm::uvec2 MAX_TEXTURE_SIZE(4096);
@@ -144,6 +147,64 @@ gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(co
return processCubeTextureColorFromImage(srcImage, srcImageName, false);
}
+
+bool isColorTexturesCompressionEnabled() {
+#if CPU_MIPMAPS
+ std::lock_guard guard(settingsMutex);
+ return compressColorTextures.get();
+#else
+ return false;
+#endif
+}
+
+bool isNormalTexturesCompressionEnabled() {
+#if CPU_MIPMAPS
+ std::lock_guard guard(settingsMutex);
+ return compressNormalTextures.get();
+#else
+ return false;
+#endif
+}
+
+bool isGrayscaleTexturesCompressionEnabled() {
+#if CPU_MIPMAPS
+ std::lock_guard guard(settingsMutex);
+ return compressGrayscaleTextures.get();
+#else
+ return false;
+#endif
+}
+
+bool isCubeTexturesCompressionEnabled() {
+#if CPU_MIPMAPS
+ std::lock_guard guard(settingsMutex);
+ return compressCubeTextures.get();
+#else
+ return false;
+#endif
+}
+
+void setColorTexturesCompressionEnabled(bool enabled) {
+ std::lock_guard guard(settingsMutex);
+ compressColorTextures.set(enabled);
+}
+
+void setNormalTexturesCompressionEnabled(bool enabled) {
+ std::lock_guard guard(settingsMutex);
+ compressNormalTextures.set(enabled);
+}
+
+void setGrayscaleTexturesCompressionEnabled(bool enabled) {
+ std::lock_guard guard(settingsMutex);
+ compressGrayscaleTextures.set(enabled);
+}
+
+void setCubeTexturesCompressionEnabled(bool enabled) {
+ std::lock_guard guard(settingsMutex);
+ compressCubeTextures.set(enabled);
+}
+
+
gpu::TexturePointer processImage(const QByteArray& content, const std::string& filename, int maxNumPixels, TextureUsage::Type textureType) {
// Help the QImage loader by extracting the image file format from the url filename ext.
// Some tga are not created properly without it.
@@ -290,6 +351,19 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) {
float inputGamma = 2.2f;
float outputGamma = 2.2f;
+ nvtt::InputOptions inputOptions;
+ inputOptions.setTextureLayout(textureType, width, height);
+ inputOptions.setMipmapData(data, width, height);
+
+ inputOptions.setFormat(inputFormat);
+ inputOptions.setGamma(inputGamma, outputGamma);
+ inputOptions.setAlphaMode(alphaMode);
+ inputOptions.setWrapMode(wrapMode);
+ inputOptions.setRoundMode(roundMode);
+
+ inputOptions.setMipmapGeneration(true);
+ inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box);
+
nvtt::CompressionOptions compressionOptions;
compressionOptions.setQuality(nvtt::Quality_Production);
@@ -346,26 +420,17 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) {
compressionOptions.setFormat(nvtt::Format_RGB);
compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
compressionOptions.setPixelFormat(8, 0, 0, 0);
+ } else if (mipFormat == gpu::Element::VEC2NU8_XY) {
+ inputOptions.setNormalMap(true);
+ compressionOptions.setFormat(nvtt::Format_RGBA);
+ compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
+ compressionOptions.setPixelFormat(8, 8, 0, 0);
} else {
qCWarning(imagelogging) << "Unknown mip format";
Q_UNREACHABLE();
return;
}
-
- nvtt::InputOptions inputOptions;
- inputOptions.setTextureLayout(textureType, width, height);
- inputOptions.setMipmapData(data, width, height);
-
- inputOptions.setFormat(inputFormat);
- inputOptions.setGamma(inputGamma, outputGamma);
- inputOptions.setAlphaMode(alphaMode);
- inputOptions.setWrapMode(wrapMode);
- inputOptions.setRoundMode(roundMode);
-
- inputOptions.setMipmapGeneration(true);
- inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box);
-
nvtt::OutputOptions outputOptions;
outputOptions.setOutputHeader(false);
MyOutputHandler outputHandler(texture, face);
@@ -424,18 +489,19 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& s
gpu::TexturePointer theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
-#if CPU_MIPMAPS && COMPRESS_COLOR_TEXTURES
+ gpu::Element formatMip;
gpu::Element formatGPU;
- if (validAlpha) {
- formatGPU = alphaAsMask ? gpu::Element::COLOR_COMPRESSED_SRGBA_MASK : gpu::Element::COLOR_COMPRESSED_SRGBA;
+ if (isColorTexturesCompressionEnabled()) {
+ if (validAlpha) {
+ formatGPU = alphaAsMask ? gpu::Element::COLOR_COMPRESSED_SRGBA_MASK : gpu::Element::COLOR_COMPRESSED_SRGBA;
+ } else {
+ formatGPU = gpu::Element::COLOR_COMPRESSED_SRGB;
+ }
+ formatMip = formatGPU;
} else {
- formatGPU = gpu::Element::COLOR_COMPRESSED_SRGB;
+ formatMip = gpu::Element::COLOR_SBGRA_32;
+ formatGPU = gpu::Element::COLOR_SRGBA_32;
}
- gpu::Element formatMip = formatGPU;
-#else
- gpu::Element formatMip = gpu::Element::COLOR_SBGRA_32;
- gpu::Element formatGPU = gpu::Element::COLOR_SRGBA_32;
-#endif
if (isStrict) {
theTexture = gpu::Texture::createStrict(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
@@ -543,14 +609,12 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(const QImag
gpu::TexturePointer theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
-
-#if CPU_MIPMAPS && COMPRESS_NORMALMAP_TEXTURES
- gpu::Element formatMip = gpu::Element::COLOR_COMPRESSED_XY;
- gpu::Element formatGPU = gpu::Element::COLOR_COMPRESSED_XY;
-#else
- gpu::Element formatMip = gpu::Element::COLOR_RGBA_32;
- gpu::Element formatGPU = gpu::Element::COLOR_RGBA_32;
-#endif
+ gpu::Element formatMip = gpu::Element::VEC2NU8_XY;
+ gpu::Element formatGPU = gpu::Element::VEC2NU8_XY;
+ if (isNormalTexturesCompressionEnabled()) {
+ formatMip = gpu::Element::COLOR_COMPRESSED_XY;
+ formatGPU = gpu::Element::COLOR_COMPRESSED_XY;
+ }
theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
theTexture->setSource(srcImageName);
@@ -576,14 +640,15 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(const QImag
gpu::TexturePointer theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
-
-#if CPU_MIPMAPS && COMPRESS_GRAYSCALE_TEXTURES
- gpu::Element formatMip = gpu::Element::COLOR_COMPRESSED_RED;
- gpu::Element formatGPU = gpu::Element::COLOR_COMPRESSED_RED;
-#else
- gpu::Element formatMip = gpu::Element::COLOR_R_8;
- gpu::Element formatGPU = gpu::Element::COLOR_R_8;
-#endif
+ gpu::Element formatMip;
+ gpu::Element formatGPU;
+ if (isGrayscaleTexturesCompressionEnabled()) {
+ formatMip = gpu::Element::COLOR_COMPRESSED_RED;
+ formatGPU = gpu::Element::COLOR_COMPRESSED_RED;
+ } else {
+ formatMip = gpu::Element::COLOR_R_8;
+ formatGPU = gpu::Element::COLOR_R_8;
+ }
theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
theTexture->setSource(srcImageName);
@@ -860,13 +925,15 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage&
image = image.convertToFormat(QImage::Format_ARGB32);
}
-#if CPU_MIPMAPS && COMPRESS_CUBEMAP_TEXTURES
- gpu::Element formatMip = gpu::Element::COLOR_COMPRESSED_SRGBA;
- gpu::Element formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA;
-#else
- gpu::Element formatMip = gpu::Element::COLOR_SRGBA_32;
- gpu::Element formatGPU = gpu::Element::COLOR_SRGBA_32;
-#endif
+ gpu::Element formatMip;
+ gpu::Element formatGPU;
+ if (isCubeTexturesCompressionEnabled()) {
+ formatMip = gpu::Element::COLOR_COMPRESSED_SRGBA;
+ formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA;
+ } else {
+ formatMip = gpu::Element::COLOR_SRGBA_32;
+ formatGPU = gpu::Element::COLOR_SRGBA_32;
+ }
// Find the layout of the cubemap in the 2D image
// Use the original image size since processSourceImage may have altered the size / aspect ratio
diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h
index 3e5aa868d2..d9dd1105cd 100644
--- a/libraries/image/src/image/Image.h
+++ b/libraries/image/src/image/Image.h
@@ -63,6 +63,16 @@ gpu::TexturePointer processCubeTextureColorFromImage(const QImage& srcImage, con
} // namespace TextureUsage
+bool isColorTexturesCompressionEnabled();
+bool isNormalTexturesCompressionEnabled();
+bool isGrayscaleTexturesCompressionEnabled();
+bool isCubeTexturesCompressionEnabled();
+
+void setColorTexturesCompressionEnabled(bool enabled);
+void setNormalTexturesCompressionEnabled(bool enabled);
+void setGrayscaleTexturesCompressionEnabled(bool enabled);
+void setCubeTexturesCompressionEnabled(bool enabled);
+
gpu::TexturePointer processImage(const QByteArray& content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType);
} // namespace image
diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp
index b22f262e85..440e2f048c 100644
--- a/libraries/ktx/src/ktx/Reader.cpp
+++ b/libraries/ktx/src/ktx/Reader.cpp
@@ -174,7 +174,7 @@ namespace ktx {
}
std::unique_ptr KTX::create(const StoragePointer& src) {
- if (!src) {
+ if (!src || !(*src)) {
return nullptr;
}
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 7745766177..9653cde7d8 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -421,7 +421,7 @@ void NetworkTexture::startRequestForNextMipLevel() {
_ktxResourceState = PENDING_MIP_REQUEST;
- init();
+ init(false);
float priority = -(float)_originalKtxDescriptor->header.numberOfMipmapLevels + (float)_lowestKnownPopulatedMip;
setLoadPriority(this, priority);
_url.setFragment(QString::number(_lowestKnownPopulatedMip - 1));
@@ -472,6 +472,10 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
void NetworkTexture::ktxHeaderRequestFinished() {
Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
+ if (!_ktxHeaderRequest) {
+ return;
+ }
+
_ktxHeaderRequestFinished = true;
maybeHandleFinishedInitialLoad();
}
@@ -479,6 +483,10 @@ void NetworkTexture::ktxHeaderRequestFinished() {
void NetworkTexture::ktxMipRequestFinished() {
Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA || _ktxResourceState == REQUESTING_MIP);
+ if (!_ktxMipRequest) {
+ return;
+ }
+
if (_ktxResourceState == LOADING_INITIAL_DATA) {
_ktxHighMipRequestFinished = true;
maybeHandleFinishedInitialLoad();
@@ -682,6 +690,27 @@ void NetworkTexture::loadContent(const QByteArray& content) {
QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _maxNumPixels));
}
+void NetworkTexture::refresh() {
+ if ((_ktxHeaderRequest || _ktxMipRequest) && !_loaded && !_failedToLoad) {
+ return;
+ }
+ if (_ktxHeaderRequest || _ktxMipRequest) {
+ if (_ktxHeaderRequest) {
+ _ktxHeaderRequest->disconnect(this);
+ _ktxHeaderRequest->deleteLater();
+ _ktxHeaderRequest = nullptr;
+ }
+ if (_ktxMipRequest) {
+ _ktxMipRequest->disconnect(this);
+ _ktxMipRequest->deleteLater();
+ _ktxMipRequest = nullptr;
+ }
+ TextureCache::requestCompleted(_self);
+ }
+
+ Resource::refresh();
+}
+
ImageReader::ImageReader(const QWeakPointer& resource, const QUrl& url, const QByteArray& data, int maxNumPixels) :
_resource(resource),
_url(url),
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index c7a7799216..aabc7fcb85 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -58,6 +58,8 @@ public:
gpu::TexturePointer getFallbackTexture() const;
+ void refresh() override;
+
signals:
void networkTextureCreated(const QWeakPointer& self);
diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp
index 092e0ccb3d..a4d5d66923 100644
--- a/libraries/networking/src/AssetResourceRequest.cpp
+++ b/libraries/networking/src/AssetResourceRequest.cpp
@@ -40,16 +40,16 @@ AssetResourceRequest::~AssetResourceRequest() {
}
}
-bool AssetResourceRequest::urlIsAssetHash() const {
+bool AssetResourceRequest::urlIsAssetHash(const QUrl& url) {
static const QString ATP_HASH_REGEX_STRING { "^atp:([A-Fa-f0-9]{64})(\\.[\\w]+)?$" };
QRegExp hashRegex { ATP_HASH_REGEX_STRING };
- return hashRegex.exactMatch(_url.toString());
+ return hashRegex.exactMatch(url.toString());
}
void AssetResourceRequest::doSend() {
// We'll either have a hash or an ATP path to a file (that maps to a hash)
- if (urlIsAssetHash()) {
+ if (urlIsAssetHash(_url)) {
// We've detected that this is a hash - simply use AssetClient to request that asset
auto parts = _url.path().split(".", QString::SkipEmptyParts);
auto hash = parts.length() > 0 ? parts[0] : "";
diff --git a/libraries/networking/src/AssetResourceRequest.h b/libraries/networking/src/AssetResourceRequest.h
index 3f110fae17..18b82f2573 100644
--- a/libraries/networking/src/AssetResourceRequest.h
+++ b/libraries/networking/src/AssetResourceRequest.h
@@ -32,7 +32,7 @@ private slots:
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
private:
- bool urlIsAssetHash() const;
+ static bool urlIsAssetHash(const QUrl& url);
void requestMappingForPath(const AssetPath& path);
void requestHash(const AssetHash& hash);
diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp
index 8d4edab2d5..88ea68780b 100644
--- a/libraries/networking/src/ResourceCache.cpp
+++ b/libraries/networking/src/ResourceCache.cpp
@@ -533,13 +533,13 @@ void Resource::ensureLoading() {
}
void Resource::setLoadPriority(const QPointer& owner, float priority) {
- if (!(_failedToLoad || _loaded)) {
+ if (!(_failedToLoad)) {
_loadPriorities.insert(owner, priority);
}
}
void Resource::setLoadPriorities(const QHash, float>& priorities) {
- if (_failedToLoad || _loaded) {
+ if (_failedToLoad) {
return;
}
for (QHash, float>::const_iterator it = priorities.constBegin();
@@ -549,7 +549,7 @@ void Resource::setLoadPriorities(const QHash, float>& prioriti
}
void Resource::clearLoadPriority(const QPointer& owner) {
- if (!(_failedToLoad || _loaded)) {
+ if (!(_failedToLoad)) {
_loadPriorities.remove(owner);
}
}
@@ -612,10 +612,12 @@ void Resource::allReferencesCleared() {
}
}
-void Resource::init() {
+void Resource::init(bool resetLoaded) {
_startedLoading = false;
_failedToLoad = false;
- _loaded = false;
+ if (resetLoaded) {
+ _loaded = false;
+ }
_attempts = 0;
_activeUrl = _url;
diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h
index 51c8d8554a..f94e1e26d2 100644
--- a/libraries/networking/src/ResourceCache.h
+++ b/libraries/networking/src/ResourceCache.h
@@ -385,7 +385,7 @@ public:
float getProgress() const { return (_bytesTotal <= 0) ? 0.0f : (float)_bytesReceived / _bytesTotal; }
/// Refreshes the resource.
- void refresh();
+ virtual void refresh();
void setSelf(const QWeakPointer& self) { _self = self; }
@@ -425,7 +425,7 @@ protected slots:
void attemptRequest();
protected:
- virtual void init();
+ virtual void init(bool resetLoaded = true);
/// Called by ResourceCache to begin loading this Resource.
/// This method can be overriden to provide custom request functionality. If this is done,
@@ -454,9 +454,14 @@ protected:
QUrl _url;
QUrl _activeUrl;
ByteRange _requestByteRange;
+
+ // _loaded == true means we are in a loaded and usable state. It is possible that there may still be
+ // active requests/loading while in this state. Example: Progressive KTX downloads, where higher resolution
+ // mips are being download.
bool _startedLoading = false;
bool _failedToLoad = false;
bool _loaded = false;
+
QHash, float> _loadPriorities;
QWeakPointer _self;
QPointer _cache;
diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp
index 439d44c940..e2c1cf2431 100644
--- a/libraries/networking/src/ResourceManager.cpp
+++ b/libraries/networking/src/ResourceManager.cpp
@@ -14,10 +14,10 @@
#include
#include
#include
+#include
#include
-
#include "AssetResourceRequest.h"
#include "FileResourceRequest.h"
#include "HTTPResourceRequest.h"
@@ -116,3 +116,51 @@ ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const Q
request->moveToThread(&_thread);
return request;
}
+
+
+bool ResourceManager::resourceExists(const QUrl& url) {
+ auto scheme = url.scheme();
+ if (scheme == URL_SCHEME_FILE) {
+ QFileInfo file { url.toString() };
+ return file.exists();
+ } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) {
+ auto& networkAccessManager = NetworkAccessManager::getInstance();
+ QNetworkRequest request { url };
+
+ request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
+ request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
+
+ auto reply = networkAccessManager.head(request);
+
+ QEventLoop loop;
+ QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+ loop.exec();
+
+ reply->deleteLater();
+
+ return reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200;
+ } else if (scheme == URL_SCHEME_ATP) {
+ auto request = new AssetResourceRequest(url);
+ ByteRange range;
+ range.fromInclusive = 1;
+ range.toExclusive = 1;
+ request->setByteRange(range);
+ request->setCacheEnabled(false);
+
+ QEventLoop loop;
+
+ QObject::connect(request, &AssetResourceRequest::finished, &loop, &QEventLoop::quit);
+
+ request->send();
+
+ loop.exec();
+
+ request->deleteLater();
+
+ return request->getResult() == ResourceRequest::Success;
+ }
+
+ qCDebug(networking) << "Unknown scheme (" << scheme << ") for URL: " << url.url();
+ return false;
+}
+
diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h
index d193c39cae..41da892701 100644
--- a/libraries/networking/src/ResourceManager.h
+++ b/libraries/networking/src/ResourceManager.h
@@ -36,6 +36,10 @@ public:
static void init();
static void cleanup();
+ // Blocking call to check if a resource exists. This function uses a QEventLoop internally
+ // to return to the calling thread so that events can still be processed.
+ static bool resourceExists(const QUrl& url);
+
private:
static QThread _thread;
diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp
index adaa7a848c..82b4bf703d 100644
--- a/libraries/networking/src/udt/PacketHeaders.cpp
+++ b/libraries/networking/src/udt/PacketHeaders.cpp
@@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityEdit:
case PacketType::EntityData:
case PacketType::EntityPhysics:
- return VERSION_ENTITIES_HINGE_CONSTRAINT;
+ return VERSION_ENTITIES_BULLET_DYNAMICS;
case PacketType::EntityQuery:
return static_cast(EntityQueryPacketVersion::JSONFilterWithFamilyTree);
case PacketType::AvatarIdentity:
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index f803b83887..746ae80361 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -208,6 +208,7 @@ const PacketVersion VERSION_ENTITIES_SERVER_SCRIPTS = 66;
const PacketVersion VERSION_ENTITIES_PHYSICS_PACKET = 67;
const PacketVersion VERSION_ENTITIES_ZONE_FILTERS = 68;
const PacketVersion VERSION_ENTITIES_HINGE_CONSTRAINT = 69;
+const PacketVersion VERSION_ENTITIES_BULLET_DYNAMICS = 70;
enum class EntityQueryPacketVersion: PacketVersion {
JSONFilter = 18,
diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp
index df7e5f87a3..8c73f43d42 100644
--- a/libraries/physics/src/ObjectActionSpring.cpp
+++ b/libraries/physics/src/ObjectActionSpring.cpp
@@ -16,6 +16,7 @@
#include "PhysicsLogging.h"
const float SPRING_MAX_SPEED = 10.0f;
+const float MAX_SPRING_TIMESCALE = 600.0f; // 10 min is a long time
const uint16_t ObjectActionSpring::springVersion = 1;
@@ -41,12 +42,65 @@ ObjectActionSpring::~ObjectActionSpring() {
#endif
}
+SpatiallyNestablePointer ObjectActionSpring::getOther() {
+ SpatiallyNestablePointer other;
+ withWriteLock([&]{
+ if (_otherID == QUuid()) {
+ // no other
+ return;
+ }
+ other = _other.lock();
+ if (other && other->getID() == _otherID) {
+ // other is already up-to-date
+ return;
+ }
+ if (other) {
+ // we have a pointer to other, but it's wrong
+ other.reset();
+ _other.reset();
+ }
+ // we have an other-id but no pointer to other cached
+ QSharedPointer parentFinder = DependencyManager::get();
+ if (!parentFinder) {
+ return;
+ }
+ EntityItemPointer ownerEntity = _ownerEntity.lock();
+ if (!ownerEntity) {
+ return;
+ }
+ bool success;
+ _other = parentFinder->find(_otherID, success, ownerEntity->getParentTree());
+ if (success) {
+ other = _other.lock();
+ }
+ });
+ return other;
+}
+
bool ObjectActionSpring::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position,
- glm::vec3& linearVelocity, glm::vec3& angularVelocity) {
- rotation = _desiredRotationalTarget;
- position = _desiredPositionalTarget;
- linearVelocity = glm::vec3();
- angularVelocity = glm::vec3();
+ glm::vec3& linearVelocity, glm::vec3& angularVelocity,
+ float& linearTimeScale, float& angularTimeScale) {
+ SpatiallyNestablePointer other = getOther();
+ withReadLock([&]{
+ linearTimeScale = _linearTimeScale;
+ angularTimeScale = _angularTimeScale;
+
+ if (!_otherID.isNull()) {
+ if (other) {
+ rotation = _desiredRotationalTarget * other->getRotation();
+ position = other->getRotation() * _desiredPositionalTarget + other->getPosition();
+ } else {
+ // we should have an "other" but can't find it, so disable the spring.
+ linearTimeScale = FLT_MAX;
+ angularTimeScale = FLT_MAX;
+ }
+ } else {
+ rotation = _desiredRotationalTarget;
+ position = _desiredPositionalTarget;
+ }
+ linearVelocity = glm::vec3();
+ angularVelocity = glm::vec3();
+ });
return true;
}
@@ -61,8 +115,10 @@ bool ObjectActionSpring::prepareForSpringUpdate(btScalar deltaTimeStep) {
glm::vec3 linearVelocity;
glm::vec3 angularVelocity;
- bool valid = false;
- int springCount = 0;
+ bool linearValid = false;
+ int linearSpringCount = 0;
+ bool angularValid = false;
+ int angularSpringCount = 0;
QList springDerivedActions;
springDerivedActions.append(ownerEntity->getActionsOfType(DYNAMIC_TYPE_SPRING));
@@ -73,41 +129,55 @@ bool ObjectActionSpring::prepareForSpringUpdate(btScalar deltaTimeStep) {
std::shared_ptr springAction = std::static_pointer_cast(action);
glm::quat rotationForAction;
glm::vec3 positionForAction;
- glm::vec3 linearVelocityForAction, angularVelocityForAction;
- bool success = springAction->getTarget(deltaTimeStep, rotationForAction,
- positionForAction, linearVelocityForAction,
- angularVelocityForAction);
+ glm::vec3 linearVelocityForAction;
+ glm::vec3 angularVelocityForAction;
+ float linearTimeScale;
+ float angularTimeScale;
+ bool success = springAction->getTarget(deltaTimeStep,
+ rotationForAction, positionForAction,
+ linearVelocityForAction, angularVelocityForAction,
+ linearTimeScale, angularTimeScale);
if (success) {
- springCount ++;
- if (springAction.get() == this) {
- // only use the rotation for this action
- valid = true;
- rotation = rotationForAction;
+ if (angularTimeScale < MAX_SPRING_TIMESCALE) {
+ angularValid = true;
+ angularSpringCount++;
+ angularVelocity += angularVelocityForAction;
+ if (springAction.get() == this) {
+ // only use the rotation for this action
+ rotation = rotationForAction;
+ }
}
- position += positionForAction;
- linearVelocity += linearVelocityForAction;
- angularVelocity += angularVelocityForAction;
+ if (linearTimeScale < MAX_SPRING_TIMESCALE) {
+ linearValid = true;
+ linearSpringCount++;
+ position += positionForAction;
+ linearVelocity += linearVelocityForAction;
+ }
}
}
- if (valid && springCount > 0) {
- position /= springCount;
- linearVelocity /= springCount;
- angularVelocity /= springCount;
-
+ if ((angularValid && angularSpringCount > 0) || (linearValid && linearSpringCount > 0)) {
withWriteLock([&]{
- _positionalTarget = position;
- _rotationalTarget = rotation;
- _linearVelocityTarget = linearVelocity;
- _angularVelocityTarget = angularVelocity;
- _positionalTargetSet = true;
- _rotationalTargetSet = true;
- _active = true;
+ if (linearValid && linearSpringCount > 0) {
+ position /= linearSpringCount;
+ linearVelocity /= linearSpringCount;
+ _positionalTarget = position;
+ _linearVelocityTarget = linearVelocity;
+ _positionalTargetSet = true;
+ _active = true;
+ }
+ if (angularValid && angularSpringCount > 0) {
+ angularVelocity /= angularSpringCount;
+ _rotationalTarget = rotation;
+ _angularVelocityTarget = angularVelocity;
+ _rotationalTargetSet = true;
+ _active = true;
+ }
});
}
- return valid;
+ return linearValid || angularValid;
}
@@ -133,8 +203,7 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
return;
}
- const float MAX_TIMESCALE = 600.0f; // 10 min is a long time
- if (_linearTimeScale < MAX_TIMESCALE) {
+ if (_linearTimeScale < MAX_SPRING_TIMESCALE) {
btVector3 targetVelocity(0.0f, 0.0f, 0.0f);
btVector3 offset = rigidBody->getCenterOfMassPosition() - glmToBullet(_positionalTarget);
float offsetLength = offset.length();
@@ -150,7 +219,7 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
rigidBody->setLinearVelocity(targetVelocity);
}
- if (_angularTimeScale < MAX_TIMESCALE) {
+ if (_angularTimeScale < MAX_SPRING_TIMESCALE) {
btVector3 targetVelocity(0.0f, 0.0f, 0.0f);
btQuaternion bodyRotation = rigidBody->getOrientation();
@@ -189,6 +258,8 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
float linearTimeScale;
glm::quat rotationalTarget;
float angularTimeScale;
+ QUuid otherID;
+
bool needUpdate = false;
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
@@ -218,11 +289,19 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
angularTimeScale = _angularTimeScale;
}
+ ok = true;
+ otherID = QUuid(EntityDynamicInterface::extractStringArgument("spring action",
+ arguments, "otherID", ok, false));
+ if (!ok) {
+ otherID = _otherID;
+ }
+
if (somethingChanged ||
positionalTarget != _desiredPositionalTarget ||
linearTimeScale != _linearTimeScale ||
rotationalTarget != _desiredRotationalTarget ||
- angularTimeScale != _angularTimeScale) {
+ angularTimeScale != _angularTimeScale ||
+ otherID != _otherID) {
// something changed
needUpdate = true;
}
@@ -234,6 +313,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
_linearTimeScale = glm::max(MIN_TIMESCALE, glm::abs(linearTimeScale));
_desiredRotationalTarget = rotationalTarget;
_angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale));
+ _otherID = otherID;
_active = true;
auto ownerEntity = _ownerEntity.lock();
@@ -256,6 +336,8 @@ QVariantMap ObjectActionSpring::getArguments() {
arguments["targetRotation"] = glmToQMap(_desiredRotationalTarget);
arguments["angularTimeScale"] = _angularTimeScale;
+
+ arguments["otherID"] = _otherID;
});
return arguments;
}
@@ -270,6 +352,7 @@ void ObjectActionSpring::serializeParameters(QDataStream& dataStream) const {
dataStream << _rotationalTargetSet;
dataStream << localTimeToServerTime(_expires);
dataStream << _tag;
+ dataStream << _otherID;
});
}
@@ -302,6 +385,8 @@ void ObjectActionSpring::deserializeParameters(QByteArray serializedArguments, Q
dataStream >> _tag;
+ dataStream >> _otherID;
+
_active = true;
});
}
diff --git a/libraries/physics/src/ObjectActionSpring.h b/libraries/physics/src/ObjectActionSpring.h
index de9562d3fa..8f810d7956 100644
--- a/libraries/physics/src/ObjectActionSpring.h
+++ b/libraries/physics/src/ObjectActionSpring.h
@@ -28,7 +28,8 @@ public:
virtual void deserialize(QByteArray serializedArguments) override;
virtual bool getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position,
- glm::vec3& linearVelocity, glm::vec3& angularVelocity);
+ glm::vec3& linearVelocity, glm::vec3& angularVelocity,
+ float& linearTimeScale, float& angularTimeScale);
protected:
static const uint16_t springVersion;
@@ -46,6 +47,10 @@ protected:
glm::vec3 _linearVelocityTarget;
glm::vec3 _angularVelocityTarget;
+ EntityItemID _otherID;
+ SpatiallyNestableWeakPointer _other;
+ SpatiallyNestablePointer getOther();
+
virtual bool prepareForSpringUpdate(btScalar deltaTimeStep);
void serializeParameters(QDataStream& dataStream) const;
diff --git a/libraries/physics/src/ObjectConstraintBallSocket.cpp b/libraries/physics/src/ObjectConstraintBallSocket.cpp
new file mode 100644
index 0000000000..35f138e840
--- /dev/null
+++ b/libraries/physics/src/ObjectConstraintBallSocket.cpp
@@ -0,0 +1,240 @@
+//
+// ObjectConstraintBallSocket.cpp
+// libraries/physics/src
+//
+// Created by Seth Alves 2017-4-29
+// 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 "QVariantGLM.h"
+
+#include "EntityTree.h"
+#include "ObjectConstraintBallSocket.h"
+#include "PhysicsLogging.h"
+
+
+const uint16_t ObjectConstraintBallSocket::constraintVersion = 1;
+
+
+ObjectConstraintBallSocket::ObjectConstraintBallSocket(const QUuid& id, EntityItemPointer ownerEntity) :
+ ObjectConstraint(DYNAMIC_TYPE_BALL_SOCKET, id, ownerEntity),
+ _pivotInA(glm::vec3(0.0f)),
+ _pivotInB(glm::vec3(0.0f))
+{
+ #if WANT_DEBUG
+ qCDebug(physics) << "ObjectConstraintBallSocket::ObjectConstraintBallSocket";
+ #endif
+}
+
+ObjectConstraintBallSocket::~ObjectConstraintBallSocket() {
+ #if WANT_DEBUG
+ qCDebug(physics) << "ObjectConstraintBallSocket::~ObjectConstraintBallSocket";
+ #endif
+}
+
+QList ObjectConstraintBallSocket::getRigidBodies() {
+ QList result;
+ result += getRigidBody();
+ QUuid otherEntityID;
+ withReadLock([&]{
+ otherEntityID = _otherEntityID;
+ });
+ if (!otherEntityID.isNull()) {
+ result += getOtherRigidBody(otherEntityID);
+ }
+ return result;
+}
+
+void ObjectConstraintBallSocket::prepareForPhysicsSimulation() {
+}
+
+void ObjectConstraintBallSocket::updateBallSocket() {
+ btPoint2PointConstraint* constraint { nullptr };
+
+ withReadLock([&]{
+ constraint = static_cast(_constraint);
+ });
+
+ if (!constraint) {
+ return;
+ }
+
+ constraint->setPivotA(glmToBullet(_pivotInA));
+ constraint->setPivotB(glmToBullet(_pivotInB));
+}
+
+
+btTypedConstraint* ObjectConstraintBallSocket::getConstraint() {
+ btPoint2PointConstraint* constraint { nullptr };
+ QUuid otherEntityID;
+ glm::vec3 pivotInA;
+ glm::vec3 pivotInB;
+
+ withReadLock([&]{
+ constraint = static_cast(_constraint);
+ pivotInA = _pivotInA;
+ otherEntityID = _otherEntityID;
+ pivotInB = _pivotInB;
+ });
+ if (constraint) {
+ return constraint;
+ }
+
+ btRigidBody* rigidBodyA = getRigidBody();
+ if (!rigidBodyA) {
+ qCDebug(physics) << "ObjectConstraintBallSocket::getConstraint -- no rigidBodyA";
+ return nullptr;
+ }
+
+ if (!otherEntityID.isNull()) {
+ // This constraint is between two entities... find the other rigid body.
+
+ btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
+ if (!rigidBodyB) {
+ return nullptr;
+ }
+
+ constraint = new btPoint2PointConstraint(*rigidBodyA, *rigidBodyB, glmToBullet(pivotInA), glmToBullet(pivotInB));
+ } else {
+ // This constraint is between an entity and the world-frame.
+
+ constraint = new btPoint2PointConstraint(*rigidBodyA, glmToBullet(pivotInA));
+ }
+
+ withWriteLock([&]{
+ _constraint = constraint;
+ });
+
+ // if we don't wake up rigidBodyA, we may not send the dynamicData property over the network
+ forceBodyNonStatic();
+ activateBody();
+
+ updateBallSocket();
+
+ return constraint;
+}
+
+
+bool ObjectConstraintBallSocket::updateArguments(QVariantMap arguments) {
+ glm::vec3 pivotInA;
+ QUuid otherEntityID;
+ glm::vec3 pivotInB;
+
+ bool needUpdate = false;
+ bool somethingChanged = ObjectDynamic::updateArguments(arguments);
+ withReadLock([&]{
+ bool ok = true;
+ pivotInA = EntityDynamicInterface::extractVec3Argument("ball-socket constraint", arguments, "pivot", ok, false);
+ if (!ok) {
+ pivotInA = _pivotInA;
+ }
+
+ ok = true;
+ otherEntityID = QUuid(EntityDynamicInterface::extractStringArgument("ball-socket constraint",
+ arguments, "otherEntityID", ok, false));
+ if (!ok) {
+ otherEntityID = _otherEntityID;
+ }
+
+ ok = true;
+ pivotInB = EntityDynamicInterface::extractVec3Argument("ball-socket constraint", arguments, "otherPivot", ok, false);
+ if (!ok) {
+ pivotInB = _pivotInB;
+ }
+
+ if (somethingChanged ||
+ pivotInA != _pivotInA ||
+ otherEntityID != _otherEntityID ||
+ pivotInB != _pivotInB) {
+ // something changed
+ needUpdate = true;
+ }
+ });
+
+ if (needUpdate) {
+ withWriteLock([&] {
+ _pivotInA = pivotInA;
+ _otherEntityID = otherEntityID;
+ _pivotInB = pivotInB;
+
+ _active = true;
+
+ auto ownerEntity = _ownerEntity.lock();
+ if (ownerEntity) {
+ ownerEntity->setDynamicDataDirty(true);
+ ownerEntity->setDynamicDataNeedsTransmit(true);
+ }
+ });
+
+ updateBallSocket();
+ }
+
+ return true;
+}
+
+QVariantMap ObjectConstraintBallSocket::getArguments() {
+ QVariantMap arguments = ObjectDynamic::getArguments();
+ withReadLock([&] {
+ if (_constraint) {
+ arguments["pivot"] = glmToQMap(_pivotInA);
+ arguments["otherEntityID"] = _otherEntityID;
+ arguments["otherPivot"] = glmToQMap(_pivotInB);
+ }
+ });
+ return arguments;
+}
+
+QByteArray ObjectConstraintBallSocket::serialize() const {
+ QByteArray serializedConstraintArguments;
+ QDataStream dataStream(&serializedConstraintArguments, QIODevice::WriteOnly);
+
+ dataStream << DYNAMIC_TYPE_BALL_SOCKET;
+ dataStream << getID();
+ dataStream << ObjectConstraintBallSocket::constraintVersion;
+
+ withReadLock([&] {
+ dataStream << localTimeToServerTime(_expires);
+ dataStream << _tag;
+
+ dataStream << _pivotInA;
+ dataStream << _otherEntityID;
+ dataStream << _pivotInB;
+ });
+
+ return serializedConstraintArguments;
+}
+
+void ObjectConstraintBallSocket::deserialize(QByteArray serializedArguments) {
+ QDataStream dataStream(serializedArguments);
+
+ EntityDynamicType type;
+ dataStream >> type;
+ assert(type == getType());
+
+ QUuid id;
+ dataStream >> id;
+ assert(id == getID());
+
+ uint16_t serializationVersion;
+ dataStream >> serializationVersion;
+ if (serializationVersion != ObjectConstraintBallSocket::constraintVersion) {
+ assert(false);
+ return;
+ }
+
+ withWriteLock([&] {
+ quint64 serverExpires;
+ dataStream >> serverExpires;
+ _expires = serverTimeToLocalTime(serverExpires);
+ dataStream >> _tag;
+
+ dataStream >> _pivotInA;
+ dataStream >> _otherEntityID;
+ dataStream >> _pivotInB;
+
+ _active = true;
+ });
+}
diff --git a/libraries/physics/src/ObjectConstraintBallSocket.h b/libraries/physics/src/ObjectConstraintBallSocket.h
new file mode 100644
index 0000000000..9e0b942a6f
--- /dev/null
+++ b/libraries/physics/src/ObjectConstraintBallSocket.h
@@ -0,0 +1,46 @@
+//
+// ObjectConstraintBallSocket.h
+// libraries/physics/src
+//
+// Created by Seth Alves 2017-4-29
+// 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_ObjectConstraintBallSocket_h
+#define hifi_ObjectConstraintBallSocket_h
+
+#include "ObjectConstraint.h"
+
+// http://bulletphysics.org/Bullet/BulletFull/classbtBallSocketConstraint.html
+
+class ObjectConstraintBallSocket : public ObjectConstraint {
+public:
+ ObjectConstraintBallSocket(const QUuid& id, EntityItemPointer ownerEntity);
+ virtual ~ObjectConstraintBallSocket();
+
+ virtual void prepareForPhysicsSimulation() override;
+
+ virtual bool updateArguments(QVariantMap arguments) override;
+ virtual QVariantMap getArguments() override;
+
+ virtual QByteArray serialize() const override;
+ virtual void deserialize(QByteArray serializedArguments) override;
+
+ virtual QList getRigidBodies() override;
+ virtual btTypedConstraint* getConstraint() override;
+
+protected:
+ static const uint16_t constraintVersion;
+
+ void updateBallSocket();
+
+ glm::vec3 _pivotInA;
+
+ EntityItemID _otherEntityID;
+ glm::vec3 _pivotInB;
+};
+
+#endif // hifi_ObjectConstraintBallSocket_h
diff --git a/libraries/physics/src/ObjectConstraintConeTwist.cpp b/libraries/physics/src/ObjectConstraintConeTwist.cpp
new file mode 100644
index 0000000000..a0a9a5fe0c
--- /dev/null
+++ b/libraries/physics/src/ObjectConstraintConeTwist.cpp
@@ -0,0 +1,367 @@
+//
+// ObjectConstraintConeTwist.cpp
+// libraries/physics/src
+//
+// Created by Seth Alves 2017-4-29
+// 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 "QVariantGLM.h"
+
+#include "EntityTree.h"
+#include "ObjectConstraintConeTwist.h"
+#include "PhysicsLogging.h"
+
+
+const uint16_t ObjectConstraintConeTwist::constraintVersion = 1;
+
+
+ObjectConstraintConeTwist::ObjectConstraintConeTwist(const QUuid& id, EntityItemPointer ownerEntity) :
+ ObjectConstraint(DYNAMIC_TYPE_CONE_TWIST, id, ownerEntity),
+ _pivotInA(glm::vec3(0.0f)),
+ _axisInA(glm::vec3(0.0f))
+{
+ #if WANT_DEBUG
+ qCDebug(physics) << "ObjectConstraintConeTwist::ObjectConstraintConeTwist";
+ #endif
+}
+
+ObjectConstraintConeTwist::~ObjectConstraintConeTwist() {
+ #if WANT_DEBUG
+ qCDebug(physics) << "ObjectConstraintConeTwist::~ObjectConstraintConeTwist";
+ #endif
+}
+
+QList ObjectConstraintConeTwist::getRigidBodies() {
+ QList result;
+ result += getRigidBody();
+ QUuid otherEntityID;
+ withReadLock([&]{
+ otherEntityID = _otherEntityID;
+ });
+ if (!otherEntityID.isNull()) {
+ result += getOtherRigidBody(otherEntityID);
+ }
+ return result;
+}
+
+void ObjectConstraintConeTwist::prepareForPhysicsSimulation() {
+}
+
+void ObjectConstraintConeTwist::updateConeTwist() {
+ btConeTwistConstraint* constraint { nullptr };
+ float swingSpan1;
+ float swingSpan2;
+ float twistSpan;
+ float softness;
+ float biasFactor;
+ float relaxationFactor;
+
+ withReadLock([&]{
+ constraint = static_cast(_constraint);
+ swingSpan1 = _swingSpan1;
+ swingSpan2 = _swingSpan2;
+ twistSpan = _twistSpan;
+ softness = _softness;
+ biasFactor = _biasFactor;
+ relaxationFactor = _relaxationFactor;
+ });
+
+ if (!constraint) {
+ return;
+ }
+
+ constraint->setLimit(swingSpan1,
+ swingSpan2,
+ twistSpan,
+ softness,
+ biasFactor,
+ relaxationFactor);
+}
+
+
+btTypedConstraint* ObjectConstraintConeTwist::getConstraint() {
+ btConeTwistConstraint* constraint { nullptr };
+ QUuid otherEntityID;
+ glm::vec3 pivotInA;
+ glm::vec3 axisInA;
+ glm::vec3 pivotInB;
+ glm::vec3 axisInB;
+
+ withReadLock([&]{
+ constraint = static_cast(_constraint);
+ pivotInA = _pivotInA;
+ axisInA = _axisInA;
+ otherEntityID = _otherEntityID;
+ pivotInB = _pivotInB;
+ axisInB = _axisInB;
+ });
+ if (constraint) {
+ return constraint;
+ }
+
+ btRigidBody* rigidBodyA = getRigidBody();
+ if (!rigidBodyA) {
+ qCDebug(physics) << "ObjectConstraintConeTwist::getConstraint -- no rigidBodyA";
+ return nullptr;
+ }
+
+ if (!otherEntityID.isNull()) {
+ // This coneTwist is between two entities... find the other rigid body.
+
+ glm::quat rotA = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInA));
+ glm::quat rotB = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInB));
+
+ btTransform frameInA(glmToBullet(rotA), glmToBullet(pivotInA));
+ btTransform frameInB(glmToBullet(rotB), glmToBullet(pivotInB));
+
+ btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
+ if (!rigidBodyB) {
+ return nullptr;
+ }
+
+ constraint = new btConeTwistConstraint(*rigidBodyA, *rigidBodyB, frameInA, frameInB);
+ } else {
+ // This coneTwist is between an entity and the world-frame.
+
+ glm::quat rot = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInA));
+
+ btTransform frameInA(glmToBullet(rot), glmToBullet(pivotInA));
+
+ constraint = new btConeTwistConstraint(*rigidBodyA, frameInA);
+ }
+
+ withWriteLock([&]{
+ _constraint = constraint;
+ });
+
+ // if we don't wake up rigidBodyA, we may not send the dynamicData property over the network
+ forceBodyNonStatic();
+ activateBody();
+
+ updateConeTwist();
+
+ return constraint;
+}
+
+
+bool ObjectConstraintConeTwist::updateArguments(QVariantMap arguments) {
+ glm::vec3 pivotInA;
+ glm::vec3 axisInA;
+ QUuid otherEntityID;
+ glm::vec3 pivotInB;
+ glm::vec3 axisInB;
+ float swingSpan1;
+ float swingSpan2;
+ float twistSpan;
+ float softness;
+ float biasFactor;
+ float relaxationFactor;
+
+ bool needUpdate = false;
+ bool somethingChanged = ObjectDynamic::updateArguments(arguments);
+ withReadLock([&]{
+ bool ok = true;
+ pivotInA = EntityDynamicInterface::extractVec3Argument("coneTwist constraint", arguments, "pivot", ok, false);
+ if (!ok) {
+ pivotInA = _pivotInA;
+ }
+
+ ok = true;
+ axisInA = EntityDynamicInterface::extractVec3Argument("coneTwist constraint", arguments, "axis", ok, false);
+ if (!ok) {
+ axisInA = _axisInA;
+ }
+
+ ok = true;
+ otherEntityID = QUuid(EntityDynamicInterface::extractStringArgument("coneTwist constraint",
+ arguments, "otherEntityID", ok, false));
+ if (!ok) {
+ otherEntityID = _otherEntityID;
+ }
+
+ ok = true;
+ pivotInB = EntityDynamicInterface::extractVec3Argument("coneTwist constraint", arguments, "otherPivot", ok, false);
+ if (!ok) {
+ pivotInB = _pivotInB;
+ }
+
+ ok = true;
+ axisInB = EntityDynamicInterface::extractVec3Argument("coneTwist constraint", arguments, "otherAxis", ok, false);
+ if (!ok) {
+ axisInB = _axisInB;
+ }
+
+ ok = true;
+ swingSpan1 = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "swingSpan1", ok, false);
+ if (!ok) {
+ swingSpan1 = _swingSpan1;
+ }
+
+ ok = true;
+ swingSpan2 = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "swingSpan2", ok, false);
+ if (!ok) {
+ swingSpan2 = _swingSpan2;
+ }
+
+ ok = true;
+ twistSpan = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "twistSpan", ok, false);
+ if (!ok) {
+ twistSpan = _twistSpan;
+ }
+
+ ok = true;
+ softness = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "softness", ok, false);
+ if (!ok) {
+ softness = _softness;
+ }
+
+ ok = true;
+ biasFactor = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "biasFactor", ok, false);
+ if (!ok) {
+ biasFactor = _biasFactor;
+ }
+
+ ok = true;
+ relaxationFactor =
+ EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "relaxationFactor", ok, false);
+ if (!ok) {
+ relaxationFactor = _relaxationFactor;
+ }
+
+ if (somethingChanged ||
+ pivotInA != _pivotInA ||
+ axisInA != _axisInA ||
+ otherEntityID != _otherEntityID ||
+ pivotInB != _pivotInB ||
+ axisInB != _axisInB ||
+ swingSpan1 != _swingSpan1 ||
+ swingSpan2 != _swingSpan2 ||
+ twistSpan != _twistSpan ||
+ softness != _softness ||
+ biasFactor != _biasFactor ||
+ relaxationFactor != _relaxationFactor) {
+ // something changed
+ needUpdate = true;
+ }
+ });
+
+ if (needUpdate) {
+ withWriteLock([&] {
+ _pivotInA = pivotInA;
+ _axisInA = axisInA;
+ _otherEntityID = otherEntityID;
+ _pivotInB = pivotInB;
+ _axisInB = axisInB;
+ _swingSpan1 = swingSpan1;
+ _swingSpan2 = swingSpan2;
+ _twistSpan = twistSpan;
+ _softness = softness;
+ _biasFactor = biasFactor;
+ _relaxationFactor = relaxationFactor;
+
+ _active = true;
+
+ auto ownerEntity = _ownerEntity.lock();
+ if (ownerEntity) {
+ ownerEntity->setDynamicDataDirty(true);
+ ownerEntity->setDynamicDataNeedsTransmit(true);
+ }
+ });
+
+ updateConeTwist();
+ }
+
+ return true;
+}
+
+QVariantMap ObjectConstraintConeTwist::getArguments() {
+ QVariantMap arguments = ObjectDynamic::getArguments();
+ withReadLock([&] {
+ if (_constraint) {
+ arguments["pivot"] = glmToQMap(_pivotInA);
+ arguments["axis"] = glmToQMap(_axisInA);
+ arguments["otherEntityID"] = _otherEntityID;
+ arguments["otherPivot"] = glmToQMap(_pivotInB);
+ arguments["otherAxis"] = glmToQMap(_axisInB);
+ arguments["swingSpan1"] = _swingSpan1;
+ arguments["swingSpan2"] = _swingSpan2;
+ arguments["twistSpan"] = _twistSpan;
+ arguments["softness"] = _softness;
+ arguments["biasFactor"] = _biasFactor;
+ arguments["relaxationFactor"] = _relaxationFactor;
+ }
+ });
+ return arguments;
+}
+
+QByteArray ObjectConstraintConeTwist::serialize() const {
+ QByteArray serializedConstraintArguments;
+ QDataStream dataStream(&serializedConstraintArguments, QIODevice::WriteOnly);
+
+ dataStream << DYNAMIC_TYPE_CONE_TWIST;
+ dataStream << getID();
+ dataStream << ObjectConstraintConeTwist::constraintVersion;
+
+ withReadLock([&] {
+ dataStream << localTimeToServerTime(_expires);
+ dataStream << _tag;
+
+ dataStream << _pivotInA;
+ dataStream << _axisInA;
+ dataStream << _otherEntityID;
+ dataStream << _pivotInB;
+ dataStream << _axisInB;
+ dataStream << _swingSpan1;
+ dataStream << _swingSpan2;
+ dataStream << _twistSpan;
+ dataStream << _softness;
+ dataStream << _biasFactor;
+ dataStream << _relaxationFactor;
+ });
+
+ return serializedConstraintArguments;
+}
+
+void ObjectConstraintConeTwist::deserialize(QByteArray serializedArguments) {
+ QDataStream dataStream(serializedArguments);
+
+ EntityDynamicType type;
+ dataStream >> type;
+ assert(type == getType());
+
+ QUuid id;
+ dataStream >> id;
+ assert(id == getID());
+
+ uint16_t serializationVersion;
+ dataStream >> serializationVersion;
+ if (serializationVersion != ObjectConstraintConeTwist::constraintVersion) {
+ assert(false);
+ return;
+ }
+
+ withWriteLock([&] {
+ quint64 serverExpires;
+ dataStream >> serverExpires;
+ _expires = serverTimeToLocalTime(serverExpires);
+ dataStream >> _tag;
+
+ dataStream >> _pivotInA;
+ dataStream >> _axisInA;
+ dataStream >> _otherEntityID;
+ dataStream >> _pivotInB;
+ dataStream >> _axisInB;
+ dataStream >> _swingSpan1;
+ dataStream >> _swingSpan2;
+ dataStream >> _twistSpan;
+ dataStream >> _softness;
+ dataStream >> _biasFactor;
+ dataStream >> _relaxationFactor;
+
+ _active = true;
+ });
+}
diff --git a/libraries/physics/src/ObjectConstraintConeTwist.h b/libraries/physics/src/ObjectConstraintConeTwist.h
new file mode 100644
index 0000000000..02297e2b91
--- /dev/null
+++ b/libraries/physics/src/ObjectConstraintConeTwist.h
@@ -0,0 +1,55 @@
+//
+// ObjectConstraintConeTwist.h
+// libraries/physics/src
+//
+// Created by Seth Alves 2017-4-23
+// 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_ObjectConstraintConeTwist_h
+#define hifi_ObjectConstraintConeTwist_h
+
+#include "ObjectConstraint.h"
+
+// http://bulletphysics.org/Bullet/BulletFull/classbtConeTwistConstraint.html
+
+class ObjectConstraintConeTwist : public ObjectConstraint {
+public:
+ ObjectConstraintConeTwist(const QUuid& id, EntityItemPointer ownerEntity);
+ virtual ~ObjectConstraintConeTwist();
+
+ virtual void prepareForPhysicsSimulation() override;
+
+ virtual bool updateArguments(QVariantMap arguments) override;
+ virtual QVariantMap getArguments() override;
+
+ virtual QByteArray serialize() const override;
+ virtual void deserialize(QByteArray serializedArguments) override;
+
+ virtual QList getRigidBodies() override;
+ virtual btTypedConstraint* getConstraint() override;
+
+protected:
+ static const uint16_t constraintVersion;
+
+ void updateConeTwist();
+
+ glm::vec3 _pivotInA;
+ glm::vec3 _axisInA;
+
+ EntityItemID _otherEntityID;
+ glm::vec3 _pivotInB;
+ glm::vec3 _axisInB;
+
+ float _swingSpan1 { TWO_PI };
+ float _swingSpan2 { TWO_PI };;
+ float _twistSpan { TWO_PI };;
+ float _softness { 1.0f };
+ float _biasFactor {0.3f };
+ float _relaxationFactor { 1.0f };
+};
+
+#endif // hifi_ObjectConstraintConeTwist_h
diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp
index 6c55d9c5dd..cf91ca904b 100644
--- a/libraries/physics/src/ObjectConstraintHinge.cpp
+++ b/libraries/physics/src/ObjectConstraintHinge.cpp
@@ -17,12 +17,12 @@
const uint16_t ObjectConstraintHinge::constraintVersion = 1;
-
+const glm::vec3 DEFAULT_HINGE_AXIS(1.0f, 0.0f, 0.0f);
ObjectConstraintHinge::ObjectConstraintHinge(const QUuid& id, EntityItemPointer ownerEntity) :
ObjectConstraint(DYNAMIC_TYPE_HINGE, id, ownerEntity),
- _pivotInA(glm::vec3(0.0f)),
- _axisInA(glm::vec3(0.0f))
+ _axisInA(DEFAULT_HINGE_AXIS),
+ _axisInB(DEFAULT_HINGE_AXIS)
{
#if WANT_DEBUG
qCDebug(physics) << "ObjectConstraintHinge::ObjectConstraintHinge";
@@ -48,23 +48,26 @@ QList ObjectConstraintHinge::getRigidBodies() {
return result;
}
+void ObjectConstraintHinge::prepareForPhysicsSimulation() {
+}
+
void ObjectConstraintHinge::updateHinge() {
btHingeConstraint* constraint { nullptr };
+ glm::vec3 axisInA;
float low;
float high;
float softness;
float biasFactor;
float relaxationFactor;
- float motorVelocity;
withReadLock([&]{
+ axisInA = _axisInA;
constraint = static_cast(_constraint);
low = _low;
high = _high;
- softness = _softness;
biasFactor = _biasFactor;
relaxationFactor = _relaxationFactor;
- motorVelocity = _motorVelocity;
+ softness = _softness;
});
if (!constraint) {
@@ -72,12 +75,6 @@ void ObjectConstraintHinge::updateHinge() {
}
constraint->setLimit(low, high, softness, biasFactor, relaxationFactor);
- if (motorVelocity != 0.0f) {
- constraint->setMotorTargetVelocity(motorVelocity);
- constraint->enableMotor(true);
- } else {
- constraint->enableMotor(false);
- }
}
@@ -107,12 +104,27 @@ btTypedConstraint* ObjectConstraintHinge::getConstraint() {
return nullptr;
}
+ if (glm::length(axisInA) < FLT_EPSILON) {
+ qCWarning(physics) << "hinge axis cannot be a zero vector";
+ axisInA = DEFAULT_HINGE_AXIS;
+ } else {
+ axisInA = glm::normalize(axisInA);
+ }
+
if (!otherEntityID.isNull()) {
// This hinge is between two entities... find the other rigid body.
btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
if (!rigidBodyB) {
return nullptr;
}
+
+ if (glm::length(axisInB) < FLT_EPSILON) {
+ qCWarning(physics) << "hinge axis cannot be a zero vector";
+ axisInB = DEFAULT_HINGE_AXIS;
+ } else {
+ axisInB = glm::normalize(axisInB);
+ }
+
constraint = new btHingeConstraint(*rigidBodyA, *rigidBodyB,
glmToBullet(pivotInA), glmToBullet(pivotInB),
glmToBullet(axisInA), glmToBullet(axisInB),
@@ -150,7 +162,6 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) {
float softness;
float biasFactor;
float relaxationFactor;
- float motorVelocity;
bool needUpdate = false;
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
@@ -217,13 +228,6 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) {
relaxationFactor = _relaxationFactor;
}
- ok = true;
- motorVelocity = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments,
- "motorVelocity", ok, false);
- if (!ok) {
- motorVelocity = _motorVelocity;
- }
-
if (somethingChanged ||
pivotInA != _pivotInA ||
axisInA != _axisInA ||
@@ -234,8 +238,7 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) {
high != _high ||
softness != _softness ||
biasFactor != _biasFactor ||
- relaxationFactor != _relaxationFactor ||
- motorVelocity != _motorVelocity) {
+ relaxationFactor != _relaxationFactor) {
// something changed
needUpdate = true;
}
@@ -253,7 +256,6 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) {
_softness = softness;
_biasFactor = biasFactor;
_relaxationFactor = relaxationFactor;
- _motorVelocity = motorVelocity;
_active = true;
@@ -284,7 +286,6 @@ QVariantMap ObjectConstraintHinge::getArguments() {
arguments["softness"] = _softness;
arguments["biasFactor"] = _biasFactor;
arguments["relaxationFactor"] = _relaxationFactor;
- arguments["motorVelocity"] = _motorVelocity;
arguments["angle"] = static_cast(_constraint)->getHingeAngle(); // [-PI,PI]
}
});
@@ -313,8 +314,6 @@ QByteArray ObjectConstraintHinge::serialize() const {
dataStream << localTimeToServerTime(_expires);
dataStream << _tag;
-
- dataStream << _motorVelocity;
});
return serializedConstraintArguments;
@@ -356,8 +355,6 @@ void ObjectConstraintHinge::deserialize(QByteArray serializedArguments) {
dataStream >> _tag;
- dataStream >> _motorVelocity;
-
_active = true;
});
}
diff --git a/libraries/physics/src/ObjectConstraintHinge.h b/libraries/physics/src/ObjectConstraintHinge.h
index 7d2cac7511..07ce8eb8a3 100644
--- a/libraries/physics/src/ObjectConstraintHinge.h
+++ b/libraries/physics/src/ObjectConstraintHinge.h
@@ -21,6 +21,8 @@ public:
ObjectConstraintHinge(const QUuid& id, EntityItemPointer ownerEntity);
virtual ~ObjectConstraintHinge();
+ virtual void prepareForPhysicsSimulation() override;
+
virtual bool updateArguments(QVariantMap arguments) override;
virtual QVariantMap getArguments() override;
@@ -42,12 +44,32 @@ protected:
glm::vec3 _pivotInB;
glm::vec3 _axisInB;
- float _low { -2.0f * PI };
- float _high { 2.0f * PI };
+ float _low { -TWO_PI };
+ float _high { TWO_PI };
+
+ // https://gamedev.stackexchange.com/questions/71436/what-are-the-parameters-for-bthingeconstraintsetlimit
+ //
+ // softness: a negative measure of the friction that determines how much the hinge rotates for a given force. A high
+ // softness would make the hinge rotate easily like it's oiled then.
+ // biasFactor: an offset for the relaxed rotation of the hinge. It won't be right in the middle of the low and high angles
+ // anymore. 1.0f is the neural value.
+ // relaxationFactor: a measure of how much force is applied internally to bring the hinge in its central rotation.
+ // This is right in the middle of the low and high angles. For example, consider a western swing door. After
+ // walking through it will swing in both directions but at the end it stays right in the middle.
+
+ // http://javadoc.jmonkeyengine.org/com/jme3/bullet/joints/HingeJoint.html
+ //
+ // _softness - the factor at which the velocity error correction starts operating, i.e. a softness of 0.9 means that
+ // the vel. corr starts at 90% of the limit range.
+ // _biasFactor - the magnitude of the position correction. It tells you how strictly the position error (drift) is
+ // corrected.
+ // _relaxationFactor - the rate at which velocity errors are corrected. This can be seen as the strength of the
+ // limits. A low value will make the the limits more spongy.
+
+
float _softness { 0.9f };
float _biasFactor { 0.3f };
float _relaxationFactor { 1.0f };
- float _motorVelocity { 0.0f };
};
#endif // hifi_ObjectConstraintHinge_h
diff --git a/libraries/physics/src/ObjectConstraintSlider.cpp b/libraries/physics/src/ObjectConstraintSlider.cpp
new file mode 100644
index 0000000000..d7d4df78af
--- /dev/null
+++ b/libraries/physics/src/ObjectConstraintSlider.cpp
@@ -0,0 +1,326 @@
+//
+// ObjectConstraintSlider.cpp
+// libraries/physics/src
+//
+// Created by Seth Alves 2017-4-23
+// 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 "QVariantGLM.h"
+
+#include "EntityTree.h"
+#include "ObjectConstraintSlider.h"
+#include "PhysicsLogging.h"
+
+
+const uint16_t ObjectConstraintSlider::constraintVersion = 1;
+
+
+ObjectConstraintSlider::ObjectConstraintSlider(const QUuid& id, EntityItemPointer ownerEntity) :
+ ObjectConstraint(DYNAMIC_TYPE_SLIDER, id, ownerEntity),
+ _pointInA(glm::vec3(0.0f)),
+ _axisInA(glm::vec3(0.0f))
+{
+}
+
+ObjectConstraintSlider::~ObjectConstraintSlider() {
+}
+
+QList ObjectConstraintSlider::getRigidBodies() {
+ QList result;
+ result += getRigidBody();
+ QUuid otherEntityID;
+ withReadLock([&]{
+ otherEntityID = _otherEntityID;
+ });
+ if (!otherEntityID.isNull()) {
+ result += getOtherRigidBody(otherEntityID);
+ }
+ return result;
+}
+
+void ObjectConstraintSlider::prepareForPhysicsSimulation() {
+}
+
+void ObjectConstraintSlider::updateSlider() {
+ btSliderConstraint* constraint { nullptr };
+
+ withReadLock([&]{
+ constraint = static_cast(_constraint);
+ });
+
+ if (!constraint) {
+ return;
+ }
+
+ // constraint->setFrames (const btTransform &frameA, const btTransform &frameB);
+ constraint->setLowerLinLimit(_linearLow);
+ constraint->setUpperLinLimit(_linearHigh);
+ constraint->setLowerAngLimit(_angularLow);
+ constraint->setUpperAngLimit(_angularHigh);
+
+}
+
+
+btTypedConstraint* ObjectConstraintSlider::getConstraint() {
+ btSliderConstraint* constraint { nullptr };
+ QUuid otherEntityID;
+ glm::vec3 pointInA;
+ glm::vec3 axisInA;
+ glm::vec3 pointInB;
+ glm::vec3 axisInB;
+
+ withReadLock([&]{
+ constraint = static_cast(_constraint);
+ pointInA = _pointInA;
+ axisInA = _axisInA;
+ otherEntityID = _otherEntityID;
+ pointInB = _pointInB;
+ axisInB = _axisInB;
+ });
+ if (constraint) {
+ return constraint;
+ }
+
+ btRigidBody* rigidBodyA = getRigidBody();
+ if (!rigidBodyA) {
+ qCDebug(physics) << "ObjectConstraintSlider::getConstraint -- no rigidBodyA";
+ return nullptr;
+ }
+
+ if (!otherEntityID.isNull()) {
+ // This slider is between two entities... find the other rigid body.
+
+ glm::quat rotA = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInA));
+ glm::quat rotB = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInB));
+
+ btTransform frameInA(glmToBullet(rotA), glmToBullet(pointInA));
+ btTransform frameInB(glmToBullet(rotB), glmToBullet(pointInB));
+
+ btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
+ if (!rigidBodyB) {
+ return nullptr;
+ }
+
+ constraint = new btSliderConstraint(*rigidBodyA, *rigidBodyB, frameInA, frameInB, true);
+ } else {
+ // This slider is between an entity and the world-frame.
+
+ glm::quat rot = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInA));
+
+ btTransform frameInA(glmToBullet(rot), glmToBullet(pointInA));
+
+ constraint = new btSliderConstraint(*rigidBodyA, frameInA, true);
+ }
+
+ withWriteLock([&]{
+ _constraint = constraint;
+ });
+
+ // if we don't wake up rigidBodyA, we may not send the dynamicData property over the network
+ forceBodyNonStatic();
+ activateBody();
+
+ updateSlider();
+
+ return constraint;
+}
+
+
+bool ObjectConstraintSlider::updateArguments(QVariantMap arguments) {
+ glm::vec3 pointInA;
+ glm::vec3 axisInA;
+ QUuid otherEntityID;
+ glm::vec3 pointInB;
+ glm::vec3 axisInB;
+ float linearLow;
+ float linearHigh;
+ float angularLow;
+ float angularHigh;
+
+ bool needUpdate = false;
+ bool somethingChanged = ObjectDynamic::updateArguments(arguments);
+ withReadLock([&]{
+ bool ok = true;
+ pointInA = EntityDynamicInterface::extractVec3Argument("slider constraint", arguments, "point", ok, false);
+ if (!ok) {
+ pointInA = _pointInA;
+ }
+
+ ok = true;
+ axisInA = EntityDynamicInterface::extractVec3Argument("slider constraint", arguments, "axis", ok, false);
+ if (!ok) {
+ axisInA = _axisInA;
+ }
+
+ ok = true;
+ otherEntityID = QUuid(EntityDynamicInterface::extractStringArgument("slider constraint",
+ arguments, "otherEntityID", ok, false));
+ if (!ok) {
+ otherEntityID = _otherEntityID;
+ }
+
+ ok = true;
+ pointInB = EntityDynamicInterface::extractVec3Argument("slider constraint", arguments, "otherPoint", ok, false);
+ if (!ok) {
+ pointInB = _pointInB;
+ }
+
+ ok = true;
+ axisInB = EntityDynamicInterface::extractVec3Argument("slider constraint", arguments, "otherAxis", ok, false);
+ if (!ok) {
+ axisInB = _axisInB;
+ }
+
+ ok = true;
+ linearLow = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, "linearLow", ok, false);
+ if (!ok) {
+ linearLow = _linearLow;
+ }
+
+ ok = true;
+ linearHigh = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, "linearHigh", ok, false);
+ if (!ok) {
+ linearHigh = _linearHigh;
+ }
+
+ ok = true;
+ angularLow = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, "angularLow", ok, false);
+ if (!ok) {
+ angularLow = _angularLow;
+ }
+
+ ok = true;
+ angularHigh = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, "angularHigh", ok, false);
+ if (!ok) {
+ angularHigh = _angularHigh;
+ }
+
+ if (somethingChanged ||
+ pointInA != _pointInA ||
+ axisInA != _axisInA ||
+ otherEntityID != _otherEntityID ||
+ pointInB != _pointInB ||
+ axisInB != _axisInB ||
+ linearLow != _linearLow ||
+ linearHigh != _linearHigh ||
+ angularLow != _angularLow ||
+ angularHigh != _angularHigh) {
+ // something changed
+ needUpdate = true;
+ }
+ });
+
+ if (needUpdate) {
+ withWriteLock([&] {
+ _pointInA = pointInA;
+ _axisInA = axisInA;
+ _otherEntityID = otherEntityID;
+ _pointInB = pointInB;
+ _axisInB = axisInB;
+ _linearLow = linearLow;
+ _linearHigh = linearHigh;
+ _angularLow = angularLow;
+ _angularHigh = angularHigh;
+
+ _active = true;
+
+ auto ownerEntity = _ownerEntity.lock();
+ if (ownerEntity) {
+ ownerEntity->setDynamicDataDirty(true);
+ ownerEntity->setDynamicDataNeedsTransmit(true);
+ }
+ });
+
+ updateSlider();
+ }
+
+ return true;
+}
+
+QVariantMap ObjectConstraintSlider::getArguments() {
+ QVariantMap arguments = ObjectDynamic::getArguments();
+ withReadLock([&] {
+ if (_constraint) {
+ arguments["point"] = glmToQMap(_pointInA);
+ arguments["axis"] = glmToQMap(_axisInA);
+ arguments["otherEntityID"] = _otherEntityID;
+ arguments["otherPoint"] = glmToQMap(_pointInB);
+ arguments["otherAxis"] = glmToQMap(_axisInB);
+ arguments["linearLow"] = _linearLow;
+ arguments["linearHigh"] = _linearHigh;
+ arguments["angularLow"] = _angularLow;
+ arguments["angularHigh"] = _angularHigh;
+ arguments["linearPosition"] = static_cast(_constraint)->getLinearPos();
+ arguments["angularPosition"] = static_cast(_constraint)->getAngularPos();
+ }
+ });
+ return arguments;
+}
+
+QByteArray ObjectConstraintSlider::serialize() const {
+ QByteArray serializedConstraintArguments;
+ QDataStream dataStream(&serializedConstraintArguments, QIODevice::WriteOnly);
+
+ dataStream << DYNAMIC_TYPE_SLIDER;
+ dataStream << getID();
+ dataStream << ObjectConstraintSlider::constraintVersion;
+
+ withReadLock([&] {
+ dataStream << localTimeToServerTime(_expires);
+ dataStream << _tag;
+
+ dataStream << _pointInA;
+ dataStream << _axisInA;
+ dataStream << _otherEntityID;
+ dataStream << _pointInB;
+ dataStream << _axisInB;
+ dataStream << _linearLow;
+ dataStream << _linearHigh;
+ dataStream << _angularLow;
+ dataStream << _angularHigh;
+ });
+
+ return serializedConstraintArguments;
+}
+
+void ObjectConstraintSlider::deserialize(QByteArray serializedArguments) {
+ QDataStream dataStream(serializedArguments);
+
+ EntityDynamicType type;
+ dataStream >> type;
+ assert(type == getType());
+
+ QUuid id;
+ dataStream >> id;
+ assert(id == getID());
+
+ uint16_t serializationVersion;
+ dataStream >> serializationVersion;
+ if (serializationVersion != ObjectConstraintSlider::constraintVersion) {
+ assert(false);
+ return;
+ }
+
+ withWriteLock([&] {
+ quint64 serverExpires;
+ dataStream >> serverExpires;
+ _expires = serverTimeToLocalTime(serverExpires);
+ dataStream >> _tag;
+
+ dataStream >> _pointInA;
+ dataStream >> _axisInA;
+ dataStream >> _otherEntityID;
+ dataStream >> _pointInB;
+ dataStream >> _axisInB;
+ dataStream >> _linearLow;
+ dataStream >> _linearHigh;
+ dataStream >> _angularLow;
+ dataStream >> _angularHigh;
+
+ _active = true;
+ });
+}
diff --git a/libraries/physics/src/ObjectConstraintSlider.h b/libraries/physics/src/ObjectConstraintSlider.h
new file mode 100644
index 0000000000..d616b9954c
--- /dev/null
+++ b/libraries/physics/src/ObjectConstraintSlider.h
@@ -0,0 +1,54 @@
+//
+// ObjectConstraintSlider.h
+// libraries/physics/src
+//
+// Created by Seth Alves 2017-4-23
+// 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_ObjectConstraintSlider_h
+#define hifi_ObjectConstraintSlider_h
+
+#include "ObjectConstraint.h"
+
+// http://bulletphysics.org/Bullet/BulletFull/classbtSliderConstraint.html
+
+class ObjectConstraintSlider : public ObjectConstraint {
+public:
+ ObjectConstraintSlider(const QUuid& id, EntityItemPointer ownerEntity);
+ virtual ~ObjectConstraintSlider();
+
+ virtual void prepareForPhysicsSimulation() override;
+
+ virtual bool updateArguments(QVariantMap arguments) override;
+ virtual QVariantMap getArguments() override;
+
+ virtual QByteArray serialize() const override;
+ virtual void deserialize(QByteArray serializedArguments) override;
+
+ virtual QList getRigidBodies() override;
+ virtual btTypedConstraint* getConstraint() override;
+
+protected:
+ static const uint16_t constraintVersion;
+
+ void updateSlider();
+
+ glm::vec3 _pointInA;
+ glm::vec3 _axisInA;
+
+ EntityItemID _otherEntityID;
+ glm::vec3 _pointInB;
+ glm::vec3 _axisInB;
+
+ float _linearLow { std::numeric_limits::max() };
+ float _linearHigh { std::numeric_limits::min() };
+
+ float _angularLow { -TWO_PI };
+ float _angularHigh { TWO_PI };
+};
+
+#endif // hifi_ObjectConstraintSlider_h
diff --git a/interface/src/avatar/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp
similarity index 94%
rename from interface/src/avatar/CauterizedMeshPartPayload.cpp
rename to libraries/render-utils/src/CauterizedMeshPartPayload.cpp
index c11f92083b..2e3d0385cd 100644
--- a/interface/src/avatar/CauterizedMeshPartPayload.cpp
+++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp
@@ -13,7 +13,7 @@
#include
-#include "SkeletonModel.h"
+#include "CauterizedModel.h"
using namespace render;
@@ -29,7 +29,7 @@ void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(
void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const {
// Still relying on the raw data from the model
- SkeletonModel* skeleton = static_cast(_model);
+ CauterizedModel* skeleton = static_cast(_model);
bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE) && skeleton->getEnableCauterization();
if (useCauterizedMesh) {
diff --git a/interface/src/avatar/CauterizedMeshPartPayload.h b/libraries/render-utils/src/CauterizedMeshPartPayload.h
similarity index 87%
rename from interface/src/avatar/CauterizedMeshPartPayload.h
rename to libraries/render-utils/src/CauterizedMeshPartPayload.h
index dc88e950c1..010cd6fcb6 100644
--- a/interface/src/avatar/CauterizedMeshPartPayload.h
+++ b/libraries/render-utils/src/CauterizedMeshPartPayload.h
@@ -1,9 +1,6 @@
//
-// CauterizedModelMeshPartPayload.h
-// interface/src/avatar
-//
// Created by AndrewMeadows 2017.01.17
-// Copyright 2017 High Fidelity, Inc.
+// Copyright 2013-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
@@ -12,7 +9,7 @@
#ifndef hifi_CauterizedMeshPartPayload_h
#define hifi_CauterizedMeshPartPayload_h
-#include
+#include "MeshPartPayload.h"
class CauterizedMeshPartPayload : public ModelMeshPartPayload {
public:
diff --git a/interface/src/avatar/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp
similarity index 98%
rename from interface/src/avatar/CauterizedModel.cpp
rename to libraries/render-utils/src/CauterizedModel.cpp
index d7b7e0fedf..14625952ea 100644
--- a/interface/src/avatar/CauterizedModel.cpp
+++ b/libraries/render-utils/src/CauterizedModel.cpp
@@ -1,7 +1,4 @@
//
-// CauterizedModel.cpp
-// interface/src/avatar
-//
// Created by Andrew Meadows 2017.01.17
// Copyright 2017 High Fidelity, Inc.
//
@@ -11,10 +8,10 @@
#include "CauterizedModel.h"
-#include
-#include
#include
+#include "AbstractViewStateInterface.h"
+#include "MeshPartPayload.h"
#include "CauterizedMeshPartPayload.h"
#include "RenderUtilsLogging.h"
diff --git a/interface/src/avatar/CauterizedModel.h b/libraries/render-utils/src/CauterizedModel.h
similarity index 84%
rename from interface/src/avatar/CauterizedModel.h
rename to libraries/render-utils/src/CauterizedModel.h
index ba12aee32b..dcff7bd12d 100644
--- a/interface/src/avatar/CauterizedModel.h
+++ b/libraries/render-utils/src/CauterizedModel.h
@@ -1,7 +1,4 @@
//
-// CauterizeableModel.h
-// interface/src/avatar
-//
// Created by Andrew Meadows 2016.01.17
// Copyright 2017 High Fidelity, Inc.
//
@@ -13,7 +10,7 @@
#define hifi_CauterizedModel_h
-#include
+#include "Model.h"
class CauterizedModel : public Model {
Q_OBJECT
@@ -31,10 +28,10 @@ public:
const std::unordered_set& getCauterizeBoneSet() const { return _cauterizeBoneSet; }
void setCauterizeBoneSet(const std::unordered_set& boneSet) { _cauterizeBoneSet = boneSet; }
- void deleteGeometry() override;
- bool updateGeometry() override;
+ void deleteGeometry() override;
+ bool updateGeometry() override;
- void createVisibleRenderItemSet() override;
+ void createVisibleRenderItemSet() override;
void createCollisionRenderItemSet() override;
virtual void updateClusterMatrices() override;
@@ -44,7 +41,7 @@ public:
protected:
std::unordered_set _cauterizeBoneSet;
- QVector _cauterizeMeshStates;
+ QVector _cauterizeMeshStates;
bool _isCauterized { false };
bool _enableCauterization { false };
};
diff --git a/libraries/render-utils/src/MaterialTextures.slh b/libraries/render-utils/src/MaterialTextures.slh
index 7b73896cc5..e694935361 100644
--- a/libraries/render-utils/src/MaterialTextures.slh
+++ b/libraries/render-utils/src/MaterialTextures.slh
@@ -64,7 +64,9 @@ float fetchRoughnessMap(vec2 uv) {
uniform sampler2D normalMap;
vec3 fetchNormalMap(vec2 uv) {
// unpack normal, swizzle to get into hifi tangent space with Y axis pointing out
- return normalize(texture(normalMap, uv).rbg -vec3(0.5, 0.5, 0.5));
+ vec2 t = 2.0 * (texture(normalMap, uv).rg - vec2(0.5, 0.5));
+ vec2 t2 = t*t;
+ return vec3(t.x, sqrt(1 - t2.x - t2.y), t.y);
}
<@endif@>
diff --git a/interface/src/avatar/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp
similarity index 97%
rename from interface/src/avatar/SoftAttachmentModel.cpp
rename to libraries/render-utils/src/SoftAttachmentModel.cpp
index 0521f7a893..8fef0f8f77 100644
--- a/interface/src/avatar/SoftAttachmentModel.cpp
+++ b/libraries/render-utils/src/SoftAttachmentModel.cpp
@@ -1,7 +1,4 @@
//
-// SoftAttachmentModel.cpp
-// interface/src/avatar
-//
// Created by Anthony J. Thibault on 12/17/15.
// Copyright 2013 High Fidelity, Inc.
//
@@ -10,7 +7,6 @@
//
#include "SoftAttachmentModel.h"
-#include "InterfaceLogging.h"
SoftAttachmentModel::SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride) :
CauterizedModel(rig, parent),
diff --git a/interface/src/avatar/SoftAttachmentModel.h b/libraries/render-utils/src/SoftAttachmentModel.h
similarity index 95%
rename from interface/src/avatar/SoftAttachmentModel.h
rename to libraries/render-utils/src/SoftAttachmentModel.h
index fea679839a..b66c1a289a 100644
--- a/interface/src/avatar/SoftAttachmentModel.h
+++ b/libraries/render-utils/src/SoftAttachmentModel.h
@@ -1,7 +1,4 @@
//
-// SoftAttachmentModel.h
-// interface/src/avatar
-//
// Created by Anthony J. Thibault on 12/17/15.
// Copyright 2015 High Fidelity, Inc.
//
diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp
index d4eeecc82e..139fe0552d 100644
--- a/libraries/script-engine/src/TabletScriptingInterface.cpp
+++ b/libraries/script-engine/src/TabletScriptingInterface.cpp
@@ -21,6 +21,8 @@
#include
#include "SoundEffect.h"
+const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system";
+const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
QScriptValue tabletToScriptValue(QScriptEngine* engine, TabletProxy* const &in) {
return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
@@ -35,11 +37,11 @@ TabletScriptingInterface::TabletScriptingInterface() {
}
QObject* TabletScriptingInterface::getSystemToolbarProxy() {
- const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system";
Qt::ConnectionType connectionType = Qt::AutoConnection;
if (QThread::currentThread() != _toolbarScriptingInterface->thread()) {
connectionType = Qt::BlockingQueuedConnection;
}
+
QObject* toolbarProxy = nullptr;
bool hasResult = QMetaObject::invokeMethod(_toolbarScriptingInterface, "getToolbar", connectionType, Q_RETURN_ARG(QObject*, toolbarProxy), Q_ARG(QString, SYSTEM_TOOLBAR));
if (hasResult) {
@@ -51,28 +53,38 @@ QObject* TabletScriptingInterface::getSystemToolbarProxy() {
}
TabletProxy* TabletScriptingInterface::getTablet(const QString& tabletId) {
+ TabletProxy* tabletProxy = nullptr;
+ {
+ // the only thing guarded should be map mutation
+ // this avoids a deadlock with the Main thread
+ // from Qt::BlockingQueuedEvent invocations later in the call-tree
+ std::lock_guard