Merge branch 'master' into 21274

This commit is contained in:
Sam Gateau 2017-05-09 11:48:57 -07:00 committed by GitHub
commit ff3cf7801a
295 changed files with 9730 additions and 4544 deletions

View file

@ -1,47 +0,0 @@
set(EXTERNAL_NAME faceshift)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://hifi-public.s3.amazonaws.com/dependencies/faceshift.zip
CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1
)
# URL_MD5 1bdcb8a0b8d5b1ede434cc41efade41d
# Hide this external target (for ide users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE FILEPATH "Path to Faceshift include directory")
set(LIBRARY_DEBUG_PATH "lib/Debug")
set(LIBRARY_RELEASE_PATH "lib/Release")
if (WIN32)
set(LIBRARY_PREFIX "")
set(LIBRARY_EXT "lib")
# use selected configuration in release path when building on Windows
set(LIBRARY_RELEASE_PATH "$<$<CONFIG:RelWithDebInfo>:build/RelWithDebInfo>")
set(LIBRARY_RELEASE_PATH "${LIBRARY_RELEASE_PATH}$<$<CONFIG:MinSizeRel>:build/MinSizeRel>")
set(LIBRARY_RELEASE_PATH "${LIBRARY_RELEASE_PATH}$<$<OR:$<CONFIG:Release>,$<CONFIG:Debug>>:lib/Release>")
elseif (APPLE)
set(LIBRARY_EXT "a")
set(LIBRARY_PREFIX "lib")
if (CMAKE_GENERATOR STREQUAL "Unix Makefiles")
set(LIBRARY_DEBUG_PATH "build")
set(LIBRARY_RELEASE_PATH "build")
endif ()
endif()
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG
${INSTALL_DIR}/${LIBRARY_DEBUG_PATH}/${LIBRARY_PREFIX}faceshift.${LIBRARY_EXT} CACHE FILEPATH "Faceshift libraries")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE
${INSTALL_DIR}/${LIBRARY_RELEASE_PATH}/${LIBRARY_PREFIX}faceshift.${LIBRARY_EXT} CACHE FILEPATH "Faceshift libraries")

View file

@ -6,8 +6,8 @@ if (WIN32)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi7.zip
URL_MD5 bc2861e50852dd590cdc773a14a041a7
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi8.zip
URL_MD5 b01510437ea15527156bc25cdf733bd9
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

View file

@ -1,14 +0,0 @@
#
# Copyright 2015 High Fidelity, Inc.
# Created by Bradley Austin Davis on 2015/10/10
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(TARGET_FACESHIFT)
add_dependency_external_projects(faceshift)
find_package(Faceshift REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${FACESHIFT_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${FACESHIFT_LIBRARIES})
add_definitions(-DHAVE_FACESHIFT)
endmacro()

View file

@ -1,26 +0,0 @@
#
# FindFaceshift.cmake
#
# Try to find the Faceshift networking library
#
# You must provide a FACESHIFT_ROOT_DIR which contains lib and include directories
#
# Once done this will define
#
# FACESHIFT_FOUND - system found Faceshift
# FACESHIFT_INCLUDE_DIRS - the Faceshift include directory
# FACESHIFT_LIBRARIES - Link this to use Faceshift
#
# Created on 8/30/2013 by Andrzej Kapolka
# Copyright 2013 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
include(SelectLibraryConfigurations)
select_library_configurations(FACESHIFT)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Faceshift DEFAULT_MSG FACESHIFT_INCLUDE_DIRS FACESHIFT_LIBRARIES)
mark_as_advanced(FACESHIFT_INCLUDE_DIRS FACESHIFT_LIBRARIES FACESHIFT_SEARCH_DIRS)

View file

@ -191,10 +191,10 @@ 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
controllers plugins image trackers
ui-plugins display-plugins input-plugins
${NON_ANDROID_LIBRARIES}
)
@ -202,7 +202,6 @@ link_hifi_libraries(
# include the binary directory of render-utils for shader includes
target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/render-utils")
#fixme find a way to express faceshift as a plugin
target_bullet()
target_opengl()
@ -210,10 +209,6 @@ if (NOT ANDROID)
target_glew()
endif ()
if (WIN32 OR APPLE)
target_faceshift()
endif()
# perform standard include and linking for found externals
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})

View file

@ -61,6 +61,11 @@
{ "from": "Standard.RightHand", "to": "Actions.RightHand" },
{ "from": "Standard.LeftFoot", "to": "Actions.LeftFoot" },
{ "from": "Standard.RightFoot", "to": "Actions.RightFoot" }
{ "from": "Standard.RightFoot", "to": "Actions.RightFoot" },
{ "from": "Standard.Hips", "to": "Actions.Hips" },
{ "from": "Standard.Spine2", "to": "Actions.Spine2" },
{ "from": "Standard.Head", "to": "Actions.Head" }
]
}

View file

@ -19,7 +19,7 @@
function shouldRaiseKeyboard() {
var nodeName = document.activeElement.nodeName;
var nodeType = document.activeElement.type;
if (nodeName === "INPUT" && (nodeType === "text" || nodeType === "number" || nodeType === "password")
if (nodeName === "INPUT" && ["email", "number", "password", "tel", "text", "url"].indexOf(nodeType) !== -1
|| document.activeElement.nodeName === "TEXTAREA") {
return true;
} else {

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 576 576" style="enable-background:new 0 0 576 576;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M359.2,249.6c16.7,43.9,33.3,87.9,50,131.8c1.3,3.4,2.5,6.7-0.2,9.9c-2.8,3.3-6.3,3-10.1,2.2
c-42.4-8.8-84.8-17.5-127.2-26.4c-5.6-1.2-10.7-1-16,1.4c-5,2.2-10.3,3.9-15.4,5.9c-8.2,3.2-9.5,7.6-4.1,14.4
c22.2,28.7,44.4,57.3,66.6,86c1.2,1.6,2.5,3.1,3.5,4.8c2.3,4.1,1.3,7.8-2.8,10.2c-1.6,0.9-3.3,1.6-5,2.2
c-15.6,5.9-31.2,11.8-46.7,17.7c-8.7,3.3-10.2,2.9-15.7-4.2c-24-31-48-62.1-72-93.2c-4.8-6.3-6.5-6.7-14-3.9c-3,1.1-5.9,2.3-8.9,3.3
c-10.9,3.5-20.5-1.1-24.6-11.7c-13-34.1-25.9-68.2-38.8-102.4c-4.5-11.7-0.2-21.5,11.6-26.2c9.8-3.9,19.6-7.5,29.4-11.2
c28-10.6,56-21.4,84.2-31.8c5.4-2,9.4-5.1,12.8-9.7c25.7-34.6,51.6-69.1,77.3-103.7c2.3-3.1,4.7-5.8,8.9-5.3c4.4,0.5,5.7,4,7,7.5
C325.7,161.4,342.4,205.5,359.2,249.6z"/>
<path class="st0" d="M348.2,141.2c-2.2,0-4.5-0.6-6.5-1.8c-5.9-3.6-7.8-11.3-4.2-17.2l39.3-64.3c3.6-5.9,11.3-7.8,17.2-4.2
c5.9,3.6,7.8,11.3,4.2,17.2l-39.3,64.3C356.5,139.1,352.4,141.2,348.2,141.2z"/>
<path class="st0" d="M504.6,363c-0.9,0-1.8-0.1-2.7-0.3L424,345.6c-6.7-1.5-11-8.1-9.5-14.9c1.5-6.7,8.1-11,14.9-9.5l77.8,17.1
c6.7,1.5,11,8.1,9.5,14.9C515.5,359,510.3,363,504.6,363z"/>
<path class="st0" d="M393.2,237.7c-4.7,0-9.2-2.7-11.3-7.2c-2.9-6.3-0.2-13.7,6.1-16.6l78.4-36.4c6.3-2.9,13.7-0.2,16.6,6.1
c2.9,6.3,0.2,13.7-6.1,16.6l-78.4,36.4C396.7,237.3,394.9,237.7,393.2,237.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -17,26 +17,26 @@ Rectangle {
property alias pixelSize: label.font.pixelSize;
property bool selected: false
property bool hovered: false
property bool enabled: false
property int spacing: 2
property var action: function () {}
property string enabledColor: hifi.colors.blueHighlight
property string disabledColor: hifi.colors.blueHighlight
property string highlightColor: hifi.colors.blueHighlight;
width: label.width + 64
height: 32
color: hifi.colors.white
enabled: false
HifiConstants { id: hifi }
RalewaySemiBold {
id: label;
color: enabledColor
color: enabled ? enabledColor : disabledColor
font.pixelSize: 15;
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
}
}
Rectangle {
id: indicator

View file

@ -0,0 +1,132 @@
import QtQuick 2.5
import QtWebEngine 1.1
import QtWebChannel 1.0
import "../controls-uit" as HiFiControls
import HFTabletWebEngineProfile 1.0
Item {
property alias url: root.url
property alias scriptURL: root.userScriptUrl
property alias eventBridge: eventBridgeWrapper.eventBridge
property alias canGoBack: root.canGoBack;
property var goBack: root.goBack;
property alias urlTag: root.urlTag
property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false
property bool keyboardRaised: false
property bool punctuationMode: false
// FIXME - Keyboard HMD only: Make Interface either set keyboardRaised property directly in OffscreenQmlSurface
// or provide HMDinfo object to QML in RenderableWebEntityItem and do the following.
/*
onKeyboardRaisedChanged: {
keyboardEnabled = HMDinfo.active;
}
*/
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
property alias viewProfile: root.profile
WebEngineView {
id: root
objectName: "webEngineView"
x: 0
y: 0
width: parent.width
height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height
profile: HFTabletWebEngineProfile {
id: webviewProfile
storageName: "qmlTabletWebEngine"
}
property string userScriptUrl: ""
// creates a global EventBridge object.
WebEngineScript {
id: createGlobalEventBridge
sourceCode: eventBridgeJavaScriptToInject
injectionPoint: WebEngineScript.DocumentCreation
worldId: WebEngineScript.MainWorld
}
// detects when to raise and lower virtual keyboard
WebEngineScript {
id: raiseAndLowerKeyboard
injectionPoint: WebEngineScript.Deferred
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
worldId: WebEngineScript.MainWorld
}
// User script.
WebEngineScript {
id: userScript
sourceUrl: root.userScriptUrl
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
worldId: WebEngineScript.MainWorld
}
property string urlTag: "noDownload=false";
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
property string newUrl: ""
webChannel.registeredObjects: [eventBridgeWrapper]
Component.onCompleted: {
// Ensure the JS from the web-engine makes it to our logging
root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
});
root.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)";
}
onFeaturePermissionRequested: {
grantFeaturePermission(securityOrigin, feature, true);
}
onLoadingChanged: {
keyboardRaised = false;
punctuationMode = false;
keyboard.resetShiftMode(false);
// Required to support clicking on "hifi://" links
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
var url = loadRequest.url.toString();
url = (url.indexOf("?") >= 0) ? url + urlTag : url + "?" + urlTag;
if (urlHandler.canHandleUrl(url)) {
if (urlHandler.handleUrl(url)) {
root.stop();
}
}
}
}
onNewViewRequested:{
// desktop is not defined for web-entities or tablet
if (typeof desktop !== "undefined") {
desktop.openBrowserWindow(request, profile);
} else {
tabletRoot.openBrowserWindow(request, profile);
}
}
}
HiFiControls.Keyboard {
id: keyboard
raised: parent.keyboardEnabled && parent.keyboardRaised
numeric: parent.punctuationMode
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
}
}

View file

@ -8,6 +8,7 @@ import "../styles" as HifiStyles
import "../styles-uit"
import "../"
import "."
Item {
id: web
HifiConstants { id: hifi }
@ -16,19 +17,20 @@ 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
property bool removingPage: false
property bool loadingPage: false
property alias webView: webview
property alias profile: webview.profile
property bool remove: false
property int currentPage: -1 // used as a model for repeater
property alias pagesModel: pagesModel
// Manage own browse history because WebEngineView history is wiped when a new URL is loaded via
// onNewViewRequested, e.g., as happens when a social media share button is clicked.
property var history: []
property int historyIndex: -1
Rectangle {
id: buttons
@ -47,21 +49,22 @@ Item {
TabletWebButton {
id: back
enabledColor: hifi.colors.baseGray
enabled: false
enabledColor: hifi.colors.darkGray
disabledColor: hifi.colors.lightGrayText
enabled: historyIndex > 0
text: "BACK"
MouseArea {
anchors.fill: parent
onClicked: goBack()
hoverEnabled: true
}
}
TabletWebButton {
id: close
enabledColor: hifi.colors.darkGray
disabledColor: hifi.colors.lightGrayText
enabled: true
text: "CLOSE"
MouseArea {
@ -71,45 +74,39 @@ Item {
}
}
RalewaySemiBold {
id: displayUrl
color: hifi.colors.baseGray
font.pixelSize: 12
verticalAlignment: Text.AlignLeft
text: webview.url
anchors {
top: nav.bottom
horizontalCenter: parent.horizontalCenter;
left: parent.left
leftMargin: 20
}
}
MouseArea {
anchors.fill: parent
preventStealing: true
propagateComposedEvents: true
}
}
ListModel {
id: pagesModel
onCountChanged: {
currentPage = count - 1;
if (currentPage > 0) {
back.enabledColor = hifi.colors.darkGray;
} else {
back.enabledColor = hifi.colors.baseGray;
}
}
}
function goBack() {
if (webview.canGoBack && !isUrlLoaded(webview.url)) {
if (currentPage > 0) {
removingPage = true;
pagesModel.remove(currentPage);
}
webview.goBack();
} else if (currentPage > 0) {
removingPage = true;
pagesModel.remove(currentPage);
if (historyIndex > 0) {
historyIndex--;
loadUrl(history[historyIndex]);
}
}
function closeWebEngine() {
if (remove) {
web.destroy();
return;
}
if (parentStackItem) {
parentStackItem.pop();
} else {
@ -118,51 +115,25 @@ Item {
}
function goForward() {
if (currentPage < pagesModel.count - 1) {
currentPage++;
if (historyIndex < history.length - 1) {
historyIndex++;
loadUrl(history[historyIndex]);
}
}
function gotoPage(url) {
urlAppend(url)
}
function isUrlLoaded(url) {
return (pagesModel.get(currentPage).webUrl === url);
}
function reloadPage() {
view.reloadAndBypassCache()
view.setActiveFocusOnPress(true);
view.setEnabled(true);
}
function urlAppend(url) {
if (removingPage) {
removingPage = false;
return;
}
var lurl = decodeURIComponent(url)
if (lurl[lurl.length - 1] !== "/") {
lurl = lurl + "/"
}
if (currentPage === -1 || (pagesModel.get(currentPage).webUrl !== lurl && !timer.running)) {
timer.start();
pagesModel.append({webUrl: lurl});
}
}
onCurrentPageChanged: {
if (currentPage >= 0 && currentPage < pagesModel.count) {
timer.start();
webview.url = pagesModel.get(currentPage).webUrl;
web.url = webview.url;
web.address = webview.url;
}
function loadUrl(url) {
webview.url = url
web.url = webview.url;
}
onUrlChanged: {
gotoPage(url)
loadUrl(url);
}
QtObject {
@ -170,18 +141,7 @@ Item {
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
Timer {
id: timer
interval: 200
running: false
repeat: false
onTriggered: timer.stop();
}
WebEngineView {
id: webview
objectName: "webEngineView"
@ -221,6 +181,7 @@ Item {
worldId: WebEngineScript.MainWorld
}
property string urlTag: "noDownload=false";
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
property string newUrl: ""
@ -234,21 +195,29 @@ 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: {
grantFeaturePermission(securityOrigin, feature, true);
}
onUrlChanged: {
// Record history, skipping null and duplicate items.
var urlString = url + "";
urlString = urlString.replace(/\//g, "%2F"); // Consistent representation of "/"s to avoid false differences.
if (urlString.length > 0 && (historyIndex === -1 || urlString !== history[historyIndex])) {
historyIndex++;
history = history.slice(0, historyIndex);
history.push(urlString);
}
}
onLoadingChanged: {
keyboardRaised = false;
punctuationMode = false;
keyboard.resetShiftMode(false);
// Required to support clicking on "hifi://" links
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
urlAppend(loadRequest.url.toString());
loadingPage = true;
var url = loadRequest.url.toString();
if (urlHandler.canHandleUrl(url)) {
if (urlHandler.handleUrl(url)) {
@ -258,10 +227,14 @@ 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) {
webview.forceActiveFocus();
}
}
onNewViewRequested: {
request.openIn(webview);
}
@ -270,6 +243,7 @@ Item {
HiFiControls.Keyboard {
id: keyboard
raised: parent.keyboardEnabled && parent.keyboardRaised
numeric: parent.punctuationMode
anchors {
left: parent.left
@ -280,7 +254,7 @@ Item {
Component.onCompleted: {
web.isDesktop = (typeof desktop !== "undefined");
address = url;
keyboardEnabled = HMD.active;
}
Keys.onPressed: {

View file

@ -113,7 +113,7 @@ Item {
if (typeof desktop !== "undefined") {
desktop.openBrowserWindow(request, profile);
} else {
console.log("onNewViewRequested: desktop not defined");
tabletRoot.openBrowserWindow(request, profile);
}
}
}

View file

@ -31,7 +31,7 @@ Item {
property bool drillDownToPlace: false;
property bool showPlace: isConcurrency;
property string messageColor: hifi.colors.blueAccent;
property string messageColor: isAnnouncement ? "white" : hifi.colors.blueAccent;
property string timePhrase: pastTime(timestamp);
property int onlineUsers: 0;
property bool isConcurrency: action === 'concurrency';
@ -71,6 +71,10 @@ Item {
property bool hasGif: imageUrl.indexOf('.gif') === (imageUrl.length - 4);
function pluralize(count, singular, optionalPlural) {
return (count === 1) ? singular : (optionalPlural || (singular + "s"));
}
DropShadow {
visible: isStacked;
anchors.fill: shadow1;
@ -115,7 +119,7 @@ Item {
id: lobby;
visible: !hasGif || (animation.status !== Image.Ready);
width: parent.width - (isConcurrency ? 0 : (2 * smallMargin));
height: parent.height - messageHeight - (isConcurrency ? 0 : smallMargin);
height: parent.height -(isAnnouncement ? smallMargin : messageHeight) - (isConcurrency ? 0 : smallMargin);
source: thumbnail || defaultThumbnail;
fillMode: Image.PreserveAspectCrop;
anchors {
@ -160,7 +164,22 @@ Item {
margins: textPadding;
}
}
Rectangle {
id: lozenge;
visible: isAnnouncement;
color: lozengeHot.containsMouse ? hifi.colors.redAccent : hifi.colors.redHighlight;
anchors.fill: infoRow;
radius: lozenge.height / 2.0;
}
Row {
id: infoRow;
Image {
id: icon;
source: isAnnouncement ? "../../images/Announce-Blast.svg" : "../../images/snap-icon.svg";
width: 40;
height: 40;
visible: ((action === 'snapshot') || isAnnouncement) && (messageHeight >= 40);
}
FiraSansRegular {
id: users;
visible: isConcurrency || isAnnouncement;
@ -169,34 +188,42 @@ Item {
color: messageColor;
anchors.verticalCenter: message.verticalCenter;
}
Image {
id: icon;
source: "../../images/snap-icon.svg"
width: 40;
height: 40;
visible: (action === 'snapshot') && (messageHeight >= 40);
}
RalewayRegular {
id: message;
text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (isAnnouncement ? "connections" : (drillDownToPlace ? "snapshots" : ("by " + userName)));
visible: !isAnnouncement;
text: isConcurrency ? pluralize(onlineUsers, "person", "people") : (drillDownToPlace ? "snapshots" : ("by " + userName));
size: textSizeSmall;
color: messageColor;
elide: Text.ElideRight; // requires a width to be specified`
width: root.width - textPadding
- (users.visible ? users.width + parent.spacing : 0)
- (icon.visible ? icon.width + parent.spacing : 0)
- (users.visible ? users.width + parent.spacing : 0)
- (actionIcon.width + (2 * smallMargin));
anchors {
bottom: parent.bottom;
bottomMargin: parent.spacing;
}
}
Column {
visible: isAnnouncement;
RalewayRegular {
text: pluralize(onlineUsers, "connection") + " "; // hack padding
size: textSizeSmall;
color: messageColor;
}
RalewayRegular {
text: pluralize(onlineUsers, "is here now", "are here now");
size: textSizeSmall * 0.7;
color: messageColor;
}
}
spacing: textPadding;
height: messageHeight;
anchors {
bottom: parent.bottom;
left: parent.left;
leftMargin: textPadding;
bottomMargin: isAnnouncement ? textPadding : 0;
}
}
// These two can be supplied to provide hover behavior.
@ -214,6 +241,7 @@ Item {
}
StateImage {
id: actionIcon;
visible: !isAnnouncement;
imageURL: "../../images/info-icon-2-state.svg";
size: 30;
buttonState: messageArea.containsMouse ? 1 : 0;
@ -223,13 +251,25 @@ Item {
margins: smallMargin;
}
}
function go() {
goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId));
}
MouseArea {
id: messageArea;
visible: !isAnnouncement;
width: parent.width;
height: messageHeight;
anchors.top: lobby.bottom;
acceptedButtons: Qt.LeftButton;
onClicked: goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId));
onClicked: go();
hoverEnabled: true;
}
MouseArea {
id: lozengeHot;
visible: lozenge.visible;
anchors.fill: lozenge;
acceptedButtons: Qt.LeftButton;
onClicked: go();
hoverEnabled: true;
}
}

View file

@ -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 {

View file

@ -34,12 +34,13 @@ Column {
property string metaverseServerUrl: '';
property string actions: 'snapshot';
onActionsChanged: fillDestinations();
Component.onCompleted: fillDestinations();
// sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick.
Component.onCompleted: delay.start();
property string labelText: actions;
property string filter: '';
onFilterChanged: filterChoicesByText();
property var goFunction: null;
property var rpc: null;
HifiConstants { id: hifi }
ListModel { id: suggestions; }
@ -81,6 +82,20 @@ Column {
property var allStories: [];
property var placeMap: ({}); // Used for making stacks.
property int requestId: 0;
function handleError(url, error, data, cb) { // cb(error) and answer truthy if needed, else falsey
if (!error && (data.status === 'success')) {
return;
}
if (!error) { // Create a message from the data
error = data.status + ': ' + data.error;
}
if (typeof(error) === 'string') { // Make a proper Error object
error = new Error(error);
}
error.message += ' in ' + url; // Include the url.
cb(error);
return true;
}
function getUserStoryPage(pageNumber, cb, cb1) { // cb(error) after all pages of domain data have been added to model
// If supplied, cb1 will be run after the first page IFF it is not the last, for responsiveness.
var options = [
@ -93,8 +108,11 @@ Column {
];
var url = metaverseBase + 'user_stories?' + options.join('&');
var thisRequestId = ++requestId;
getRequest(url, function (error, data) {
if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) {
rpc('request', url, function (error, data) {
if (thisRequestId !== requestId) {
error = 'stale';
}
if (handleError(url, error, data, cb)) {
return; // abandon stale requests
}
allStories = allStories.concat(data.user_stories.map(makeModelData));
@ -107,14 +125,21 @@ Column {
cb();
});
}
property var delay: Timer { // No setTimeout or nextTick in QML.
interval: 0;
onTriggered: fillDestinations();
}
function fillDestinations() { // Public
function report(label, error) {
console.log(label, actions, error || 'ok', allStories.length, 'filtered to', suggestions.count);
}
var filter = makeFilteredStoryProcessor(), counter = 0;
allStories = [];
suggestions.clear();
placeMap = {};
getUserStoryPage(1, function (error) {
allStories.slice(counter).forEach(filter);
console.log('user stories query', actions, error || 'ok', allStories.length, 'filtered to', suggestions.count);
report('user stories update', error);
root.visible = !!suggestions.count;
}, function () { // If there's more than a page, put what we have in the model right away, keeping track of how many are processed.
allStories.forEach(function (story) {
@ -122,15 +147,17 @@ Column {
filter(story);
root.visible = !!suggestions.count;
});
report('user stories');
});
}
function identity(x) {
return x;
}
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) {

View file

@ -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

View file

@ -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

View file

@ -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();

View file

@ -33,9 +33,27 @@ StackView {
property int cardWidth: 212;
property int cardHeight: 152;
property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/";
property var tablet: null;
// This version only implements rpc(method, parameters, callback(error, result)) calls initiated from here, not initiated from .js, nor "notifications".
property var rpcCalls: ({});
property var rpcCounter: 0;
signal sendToScript(var message);
function rpc(method, parameters, callback) {
rpcCalls[rpcCounter] = callback;
var message = {method: method, params: parameters, id: rpcCounter++, jsonrpc: "2.0"};
sendToScript(message);
}
function fromScript(message) {
var callback = rpcCalls[message.id];
if (!callback) {
console.log('No callback for message fromScript', JSON.stringify(message));
return;
}
delete rpcCalls[message.id];
callback(message.error, message.result);
}
Component { id: tabletWebView; TabletWebView {} }
Component.onCompleted: {
updateLocationText(false);
@ -54,7 +72,7 @@ StackView {
}
function resetAfterTeleport() {
function resetAfterTeleport() {
//storyCardFrame.shown = root.shown = false;
}
function goCard(targetString) {
@ -262,10 +280,10 @@ StackView {
cardHeight: 163 + (2 * 4);
metaverseServerUrl: addressBarDialog.metaverseServerUrl;
labelText: 'HAPPENING NOW';
//actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing.
actions: 'announcement';
filter: addressLine.text;
goFunction: goCard;
rpc: root.rpc;
}
Feed {
id: places;
@ -278,6 +296,7 @@ StackView {
actions: 'concurrency';
filter: addressLine.text;
goFunction: goCard;
rpc: root.rpc;
}
Feed {
id: snapshots;
@ -291,6 +310,7 @@ StackView {
actions: 'snapshot';
filter: addressLine.text;
goFunction: goCard;
rpc: root.rpc;
}
}
}
@ -330,50 +350,6 @@ StackView {
}
function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects.
// TODO: make available to other .qml.
var request = new XMLHttpRequest();
// QT bug: apparently doesn't handle onload. Workaround using readyState.
request.onreadystatechange = function () {
var READY_STATE_DONE = 4;
var HTTP_OK = 200;
if (request.readyState >= READY_STATE_DONE) {
var error = (request.status !== HTTP_OK) && request.status.toString() + ':' + request.statusText,
response = !error && request.responseText,
contentType = !error && request.getResponseHeader('content-type');
if (!error && contentType.indexOf('application/json') === 0) {
try {
response = JSON.parse(response);
} catch (e) {
error = e;
}
}
cb(error, response);
}
};
request.open("GET", url, true);
request.send();
}
function identity(x) {
return x;
}
function handleError(url, error, data, cb) { // cb(error) and answer truthy if needed, else falsey
if (!error && (data.status === 'success')) {
return;
}
if (!error) { // Create a message from the data
error = data.status + ': ' + data.error;
}
if (typeof(error) === 'string') { // Make a proper Error object
error = new Error(error);
}
error.message += ' in ' + url; // Include the url.
cb(error);
return true;
}
function updateLocationText(enteringAddress) {
if (enteringAddress) {
notice.text = "Go To a place, @user, path, or network address:";

View file

@ -1,7 +1,9 @@
import QtQuick 2.0
import Hifi 1.0
import QtQuick.Controls 1.4
import HFTabletWebEngineProfile 1.0
import "../../dialogs"
import "../../controls"
Item {
id: tabletRoot
@ -11,6 +13,7 @@ Item {
property var rootMenu;
property var openModal: null;
property var openMessage: null;
property var openBrowser: null;
property string subMenu: ""
signal showDesktop();
property bool shown: true
@ -87,13 +90,18 @@ Item {
loader.item.gotoPreviousApp = true;
}
}
function loadWebBase() {
loader.source = "";
loader.source = "TabletWebView.qml";
}
function returnToPreviousApp() {
tabletApps.remove(currentApp);
var isWebPage = tabletApps.get(currentApp).isWebUrl;
if (isWebPage) {
var webUrl = tabletApps.get(currentApp).appWebUrl;
var scriptUrl = tabletApps.get(currentApp).scriptUrl;
var webUrl = tabletApps.get(currentApp).appWebUrl;
var scriptUrl = tabletApps.get(currentApp).scriptUrl;
loadSource("TabletWebView.qml");
loadWebUrl(webUrl, scriptUrl);
} else {
@ -101,6 +109,16 @@ Item {
}
}
function openBrowserWindow(request, profile) {
var component = Qt.createComponent("../../controls/TabletWebView.qml");
var newWindow = component.createObject(tabletRoot);
newWindow.eventBridge = tabletRoot.eventBridge;
newWindow.remove = true;
newWindow.profile = profile;
request.openIn(newWindow.webView);
tabletRoot.openBrowser = newWindow;
}
function loadWebUrl(url, injectedJavaScriptUrl) {
tabletApps.clear();
loader.item.url = url;
@ -180,6 +198,11 @@ Item {
openModal.destroy();
openModal = null;
}
if (openBrowser) {
openBrowser.destroy();
openBrowser = null;
}
}
}

View file

@ -3,7 +3,7 @@ import QtWebEngine 1.2
import "../../controls" as Controls
Controls.WebView {
Controls.TabletWebScreen {
}

View file

@ -38,6 +38,11 @@ Windows.ScrollingWindow {
loader.source = url;
}
function loadWebBase() {
loader.source = "";
loader.source = "WindowWebView.qml";
}
function loadWebUrl(url, injectedJavaScriptUrl) {
loader.item.url = url;
loader.item.scriptURL = injectedJavaScriptUrl;

View file

@ -0,0 +1,10 @@
import QtQuick 2.0
import QtWebEngine 1.2
import "../../controls" as Controls
Controls.WebView {
}

View file

@ -128,16 +128,15 @@
#include <QmlWebWindowClass.h>
#include <Preferences.h>
#include <display-plugins/CompositorHelper.h>
#include <trackers/EyeTracker.h>
#include <avatars-renderer/ScriptAvatar.h>
#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/EyeTracker.h"
#include "devices/Faceshift.h"
#include "devices/Leapmotion.h"
#include "DiscoverabilityManager.h"
#include "GLCanvas.h"
@ -480,7 +479,6 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<ModelCache>();
DependencyManager::set<ScriptCache>();
DependencyManager::set<SoundCache>();
DependencyManager::set<Faceshift>();
DependencyManager::set<DdeFaceTracker>();
DependencyManager::set<EyeTracker>();
DependencyManager::set<AudioClient>();
@ -1210,10 +1208,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
this->installEventFilter(this);
// initialize our face trackers after loading the menu settings
auto faceshiftTracker = DependencyManager::get<Faceshift>();
faceshiftTracker->init();
connect(faceshiftTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled);
#ifdef HAVE_DDE
auto ddeTracker = DependencyManager::get<DdeFaceTracker>();
ddeTracker->init();
@ -1592,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) {
@ -2197,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()) {
@ -2532,6 +2528,11 @@ bool Application::event(QEvent* event) {
isPaintingThrottled = false;
return true;
} else if ((int)event->type() == (int)Idle) {
float nsecsElapsed = (float)_lastTimeUpdated.nsecsElapsed();
idle(nsecsElapsed);
return true;
}
@ -3626,20 +3627,13 @@ ivec2 Application::getMouse() const {
}
FaceTracker* Application::getActiveFaceTracker() {
auto faceshift = DependencyManager::get<Faceshift>();
auto dde = DependencyManager::get<DdeFaceTracker>();
return (dde->isActive() ? static_cast<FaceTracker*>(dde.data()) :
(faceshift->isActive() ? static_cast<FaceTracker*>(faceshift.data()) : nullptr));
return dde->isActive() ? static_cast<FaceTracker*>(dde.data()) : nullptr;
}
FaceTracker* Application::getSelectedFaceTracker() {
FaceTracker* faceTracker = nullptr;
#ifdef HAVE_FACESHIFT
if (Menu::getInstance()->isOptionChecked(MenuOption::Faceshift)) {
faceTracker = DependencyManager::get<Faceshift>().data();
}
#endif
#ifdef HAVE_DDE
if (Menu::getInstance()->isOptionChecked(MenuOption::UseCamera)) {
faceTracker = DependencyManager::get<DdeFaceTracker>().data();
@ -3649,15 +3643,8 @@ FaceTracker* Application::getSelectedFaceTracker() {
}
void Application::setActiveFaceTracker() const {
#if defined(HAVE_FACESHIFT) || defined(HAVE_DDE)
bool isMuted = Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking);
#endif
#ifdef HAVE_FACESHIFT
auto faceshiftTracker = DependencyManager::get<Faceshift>();
faceshiftTracker->setIsMuted(isMuted);
faceshiftTracker->setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) && !isMuted);
#endif
#ifdef HAVE_DDE
bool isMuted = Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking);
bool isUsingDDE = Menu::getInstance()->isOptionChecked(MenuOption::UseCamera);
Menu::getInstance()->getActionForOption(MenuOption::BinaryEyelidControl)->setVisible(isUsingDDE);
Menu::getInstance()->getActionForOption(MenuOption::CoupleEyelids)->setVisible(isUsingDDE);
@ -4102,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) {
@ -4320,13 +4331,6 @@ void Application::update(float deltaTime) {
if (nearbyEntitiesAreReadyForPhysics()) {
_physicsEnabled = true;
getMyAvatar()->updateMotionBehaviorFromMenu();
} else {
auto characterController = getMyAvatar()->getCharacterController();
if (characterController) {
// if we have a character controller, disable it here so the avatar doesn't get stuck due to
// a non-loading collision hull.
characterController->setEnabled(false);
}
}
}
} else if (domainLoadingInProgress) {
@ -4375,7 +4379,13 @@ void Application::update(float deltaTime) {
controller::InputCalibrationData calibrationData = {
myAvatar->getSensorToWorldMatrix(),
createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()),
myAvatar->getHMDSensorMatrix()
myAvatar->getHMDSensorMatrix(),
myAvatar->getCenterEyeCalibrationMat(),
myAvatar->getHeadCalibrationMat(),
myAvatar->getSpine2CalibrationMat(),
myAvatar->getHipsCalibrationMat(),
myAvatar->getLeftFootCalibrationMat(),
myAvatar->getRightFootCalibrationMat()
};
InputPluginPointer keyboardMousePlugin;
@ -4423,6 +4433,13 @@ void Application::update(float deltaTime) {
controller::Pose rightFootPose = userInputMapper->getPoseState(controller::Action::RIGHT_FOOT);
myAvatar->setFootControllerPosesInSensorFrame(leftFootPose.transform(avatarToSensorMatrix), rightFootPose.transform(avatarToSensorMatrix));
controller::Pose hipsPose = userInputMapper->getPoseState(controller::Action::HIPS);
controller::Pose spine2Pose = userInputMapper->getPoseState(controller::Action::SPINE2);
myAvatar->setSpineControllerPosesInSensorFrame(hipsPose.transform(avatarToSensorMatrix), spine2Pose.transform(avatarToSensorMatrix));
controller::Pose headPose = userInputMapper->getPoseState(controller::Action::HEAD);
myAvatar->setHeadControllerPoseInSensorFrame(headPose.transform(avatarToSensorMatrix));
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
updateDialogs(deltaTime); // update various stats dialogs if present
@ -5133,7 +5150,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
}
void Application::resetSensors(bool andReload) {
DependencyManager::get<Faceshift>()->reset();
DependencyManager::get<DdeFaceTracker>()->reset();
DependencyManager::get<EyeTracker>()->reset();
getActiveDisplayPlugin()->resetSensors();
@ -5431,9 +5447,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
entityScriptingInterface->setPacketSender(&_entityEditSender);
entityScriptingInterface->setEntityTree(getEntities()->getTree());
// AvatarManager has some custom types
AvatarManager::registerMetaTypes(scriptEngine);
// give the script engine to the RecordingScriptingInterface for its callbacks
DependencyManager::get<RecordingScriptingInterface>()->setScriptEngine(scriptEngine);
@ -5441,7 +5454,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
@ -5540,6 +5552,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
auto entityScriptServerLog = DependencyManager::get<EntityScriptServerLogClient>();
scriptEngine->registerGlobalObject("EntityScriptServerLog", entityScriptServerLog.data());
scriptEngine->registerGlobalObject("AvatarInputs", AvatarInputs::getInstance());
qScriptRegisterMetaType(scriptEngine, OverlayIDtoScriptValue, OverlayIDfromScriptValue);
@ -6502,10 +6516,22 @@ void Application::activeChanged(Qt::ApplicationState state) {
}
void Application::windowMinimizedChanged(bool minimized) {
// initialize the _minimizedWindowTimer
static std::once_flag once;
std::call_once(once, [&] {
connect(&_minimizedWindowTimer, &QTimer::timeout, this, [] {
QCoreApplication::postEvent(QCoreApplication::instance(), new QEvent(static_cast<QEvent::Type>(Idle)), Qt::HighEventPriority);
});
});
// avoid rendering to the display plugin but continue posting Idle events,
// so that physics continues to simulate and the deadlock watchdog knows we're alive
if (!minimized && !getActiveDisplayPlugin()->isActive()) {
_minimizedWindowTimer.stop();
getActiveDisplayPlugin()->activate();
} else if (minimized && getActiveDisplayPlugin()->isActive()) {
getActiveDisplayPlugin()->deactivate();
_minimizedWindowTimer.start(THROTTLED_SIM_FRAME_PERIOD_MS);
}
}

View file

@ -137,8 +137,9 @@ public:
enum Event {
Present = DisplayPlugin::Present,
Paint = Present + 1,
Lambda = Paint + 1
Paint,
Idle,
Lambda
};
// FIXME? Empty methods, do we still need them?
@ -372,6 +373,7 @@ public slots:
static void showHelp();
void cycleCamera();
void cameraModeChanged();
void cameraMenuChanged();
void toggleOverlays();
void setOverlaysVisible(bool visible);
@ -536,6 +538,7 @@ private:
RateCounter<> _avatarSimCounter;
RateCounter<> _simCounter;
QTimer _minimizedWindowTimer;
QElapsedTimer _timerStart;
QElapsedTimer _lastTimeUpdated;

View file

@ -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) {

View file

@ -11,11 +11,10 @@
#ifndef hifi_FancyCamera_h
#define hifi_FancyCamera_h
#include "Camera.h"
#include <shared/Camera.h>
#include <EntityItem.h>
#include <EntityTypes.h>
// TODO: come up with a better name than "FancyCamera"
class FancyCamera : public Camera {
Q_OBJECT
@ -31,6 +30,8 @@ public:
FancyCamera() : Camera() {}
EntityItemPointer getCameraEntityPointer() const { return _cameraEntity; }
PickRay computePickRay(float x, float y) const override;
public slots:
QUuid getCameraEntity() const;

View file

@ -17,6 +17,9 @@
#include <ObjectActionSpring.h>
#include <ObjectActionTravelOriented.h>
#include <ObjectConstraintHinge.h>
#include <ObjectConstraintSlider.h>
#include <ObjectConstraintBallSocket.h>
#include <ObjectConstraintConeTwist.h>
#include <LogHandler.h>
#include "InterfaceDynamicFactory.h"
@ -38,9 +41,15 @@ EntityDynamicPointer interfaceDynamicFactory(EntityDynamicType type, const QUuid
return std::make_shared<ObjectConstraintHinge>(id, ownerEntity);
case DYNAMIC_TYPE_FAR_GRAB:
return std::make_shared<AvatarActionFarGrab>(id, ownerEntity);
case DYNAMIC_TYPE_SLIDER:
return std::make_shared<ObjectConstraintSlider>(id, ownerEntity);
case DYNAMIC_TYPE_BALL_SOCKET:
return std::make_shared<ObjectConstraintBallSocket>(id, ownerEntity);
case DYNAMIC_TYPE_CONE_TWIST:
return std::make_shared<ObjectConstraintConeTwist>(id, ownerEntity);
}
Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity dynamic type");
qDebug() << "Unknown entity dynamic type";
return EntityDynamicPointer();
}

View file

@ -34,7 +34,6 @@
#include "avatar/AvatarManager.h"
#include "AvatarBookmarks.h"
#include "devices/DdeFaceTracker.h"
#include "devices/Faceshift.h"
#include "MainWindow.h"
#include "render/DrawStatus.h"
#include "scripting/MenuScriptingInterface.h"
@ -198,7 +197,7 @@ Menu::Menu() {
0, // QML Qt::Key_Apostrophe,
qApp, SLOT(resetSensors()));
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableCharacterController, 0, true,
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableAvatarCollisions, 0, true,
avatar.get(), SLOT(updateMotionBehaviorFromMenu()));
// Avatar > AvatarBookmarks related menus -- Note: the AvatarBookmarks class adds its own submenus here.
@ -451,12 +450,6 @@ Menu::Menu() {
qApp, SLOT(setActiveFaceTracker()));
faceTrackerGroup->addAction(noFaceTracker);
#ifdef HAVE_FACESHIFT
QAction* faceshiftFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::Faceshift,
0, false,
qApp, SLOT(setActiveFaceTracker()));
faceTrackerGroup->addAction(faceshiftFaceTracker);
#endif
#ifdef HAVE_DDE
QAction* ddeFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseCamera,
0, true,
@ -477,11 +470,10 @@ Menu::Menu() {
QAction* ddeCalibrate = addActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::CalibrateCamera, 0,
DependencyManager::get<DdeFaceTracker>().data(), SLOT(calibrate()));
ddeCalibrate->setVisible(true); // DDE face tracking is on by default
#endif
#if defined(HAVE_FACESHIFT) || defined(HAVE_DDE)
faceTrackingMenu->addSeparator();
addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::MuteFaceTracking,
Qt::CTRL | Qt::SHIFT | Qt::Key_F, true); // DDE face tracking is on by default
[](bool mute) { FaceTracker::setIsMuted(mute); },
Qt::CTRL | Qt::SHIFT | Qt::Key_F, FaceTracker::isMuted());
addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::AutoMuteAudio, 0, false);
#endif
@ -503,12 +495,15 @@ Menu::Menu() {
qApp, SLOT(setActiveEyeTracker()));
#endif
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false,
avatarManager.data(), SLOT(setShouldShowReceiveStats(bool)));
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false);
connect(action, &QAction::triggered, [this]{ Avatar::setShowReceiveStats(isOptionChecked(MenuOption::AvatarReceiveStats)); });
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowBoundingCollisionShapes, 0, false);
connect(action, &QAction::triggered, [this]{ Avatar::setShowCollisionShapes(isOptionChecked(MenuOption::ShowBoundingCollisionShapes)); });
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowMyLookAtVectors, 0, false);
connect(action, &QAction::triggered, [this]{ Avatar::setShowMyLookAtVectors(isOptionChecked(MenuOption::ShowMyLookAtVectors)); });
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false);
connect(action, &QAction::triggered, [this]{ Avatar::setShowOtherLookAtVectors(isOptionChecked(MenuOption::ShowOtherLookAtVectors)); });
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderBoundingCollisionShapes);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderMyLookAtVectors, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderOtherLookAtVectors, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false,
avatar.get(), SLOT(setEnableDebugDrawDefaultPose(bool)));

View file

@ -96,7 +96,7 @@ namespace MenuOption {
const QString DontRenderEntitiesAsScene = "Don't Render Entities as Scene";
const QString EchoLocalAudio = "Echo Local Audio";
const QString EchoServerAudio = "Echo Server Audio";
const QString EnableCharacterController = "Collide with world";
const QString EnableAvatarCollisions = "Enable Avatar Collisions";
const QString EnableInverseKinematics = "Enable Inverse Kinematics";
const QString EntityScriptServerLog = "Entity Script Server Log";
const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation";
@ -105,7 +105,6 @@ namespace MenuOption {
const QString ExpandPaintGLTiming = "Expand /paintGL";
const QString ExpandPhysicsSimulationTiming = "Expand /physics";
const QString ExpandUpdateTiming = "Expand /update";
const QString Faceshift = "Faceshift";
const QString FirstPerson = "First Person";
const QString FivePointCalibration = "5 Point Calibration";
const QString FixGaze = "Fix Gaze (no saccade)";
@ -146,9 +145,6 @@ namespace MenuOption {
const QString Quit = "Quit";
const QString ReloadAllScripts = "Reload All Scripts";
const QString ReloadContent = "Reload Content (Clears all caches)";
const QString RenderBoundingCollisionShapes = "Show Bounding Collision Shapes";
const QString RenderMyLookAtVectors = "Show My Eye Vectors";
const QString RenderOtherLookAtVectors = "Show Other Eye Vectors";
const QString RenderMaxTextureMemory = "Maximum Texture Memory";
const QString RenderMaxTextureAutomatic = "Automatic Texture Memory";
const QString RenderMaxTexture4MB = "4 MB";
@ -174,8 +170,11 @@ namespace MenuOption {
const QString SendWrongDSConnectVersion = "Send wrong DS connect version";
const QString SendWrongProtocolVersion = "Send wrong protocol version";
const QString SetHomeLocation = "Set Home Location";
const QString ShowDSConnectTable = "Show Domain Connection Timing";
const QString ShowBordersEntityNodes = "Show Entity Nodes";
const QString ShowBoundingCollisionShapes = "Show Bounding Collision Shapes";
const QString ShowDSConnectTable = "Show Domain Connection Timing";
const QString ShowMyLookAtVectors = "Show My Eye Vectors";
const QString ShowOtherLookAtVectors = "Show Other Eye Vectors";
const QString ShowRealtimeEntityStats = "Show Realtime Entity Stats";
const QString StandingHMDSensorMode = "Standing HMD Sensor Mode";
const QString SimulateEyeTracking = "Simulate";

View file

@ -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?

View file

@ -17,9 +17,6 @@
#include <gpu/Batch.h>
float randFloat();
const glm::vec3 randVector();
void renderWorldBox(gpu::Batch& batch);
void runTimingTests();

View file

@ -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<AvatarManager>();
auto holdingAvatar = std::static_pointer_cast<Avatar>(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;

View file

@ -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;

View file

@ -32,9 +32,9 @@
#include <SettingHandle.h>
#include <UsersScriptingInterface.h>
#include <UUID.h>
#include <avatars-renderer/OtherAvatar.h>
#include "Application.h"
#include "Avatar.h"
#include "AvatarManager.h"
#include "InterfaceLogging.h"
#include "Menu.h"
@ -50,23 +50,6 @@ static const quint64 MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS = USECS_PER_SECOND /
// We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key.
const QUuid MY_AVATAR_KEY; // NULL key
static QScriptValue localLightToScriptValue(QScriptEngine* engine, const AvatarManager::LocalLight& light) {
QScriptValue object = engine->newObject();
object.setProperty("direction", vec3toScriptValue(engine, light.direction));
object.setProperty("color", vec3toScriptValue(engine, light.color));
return object;
}
static void localLightFromScriptValue(const QScriptValue& value, AvatarManager::LocalLight& light) {
vec3FromScriptValue(value.property("direction"), light.direction);
vec3FromScriptValue(value.property("color"), light.color);
}
void AvatarManager::registerMetaTypes(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, localLightToScriptValue, localLightFromScriptValue);
qScriptRegisterSequenceMetaType<QVector<AvatarManager::LocalLight> >(engine);
}
AvatarManager::AvatarManager(QObject* parent) :
_avatarsToFade(),
_myAvatar(std::make_shared<MyAvatar>(qApp->thread(), std::make_shared<Rig>()))
@ -91,6 +74,7 @@ AvatarManager::AvatarManager(QObject* parent) :
}
AvatarManager::~AvatarManager() {
assert(_motionStates.empty());
}
void AvatarManager::init() {
@ -145,10 +129,9 @@ float AvatarManager::getAvatarUpdateRate(const QUuid& sessionID, const QString&
float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QString& rateName) const {
auto avatar = std::static_pointer_cast<Avatar>(getAvatarBySessionID(sessionID));
return avatar ? avatar->getSimulationRate(rateName) : 0.0f;
return avatar ? avatar->getSimulationRate(rateName) : 0.0f;
}
void AvatarManager::updateOtherAvatars(float deltaTime) {
// lock the hash for read to check the size
QReadLocker lock(&_hashLock);
@ -200,16 +183,15 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
if (_shouldRender) {
avatar->ensureInScene(avatar, qApp->getMain3DScene());
}
if (!avatar->getMotionState()) {
if (!avatar->isInPhysicsSimulation()) {
ShapeInfo shapeInfo;
avatar->computeShapeInfo(shapeInfo);
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
// don't add to the simulation now, instead put it on a list to be added later
AvatarMotionState* motionState = new AvatarMotionState(avatar.get(), shape);
avatar->setMotionState(motionState);
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);
avatar->setPhysicsCallback([=] (uint32_t flags) { motionState->addDirtyFlags(flags); });
_motionStates.insert(avatar.get(), motionState);
_motionStatesToAddToPhysics.insert(motionState);
_motionStatesThatMightUpdate.insert(motionState);
}
}
avatar->animateScaleChanges(deltaTime);
@ -294,50 +276,44 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
const float MIN_FADE_SCALE = MIN_AVATAR_SCALE;
QReadLocker locker(&_hashLock);
QVector<AvatarSharedPointer>::iterator itr = _avatarsToFade.begin();
while (itr != _avatarsToFade.end()) {
auto avatar = std::static_pointer_cast<Avatar>(*itr);
QVector<AvatarSharedPointer>::iterator avatarItr = _avatarsToFade.begin();
while (avatarItr != _avatarsToFade.end()) {
auto avatar = std::static_pointer_cast<Avatar>(*avatarItr);
avatar->setTargetScale(avatar->getUniformScale() * SHRINK_RATE);
avatar->animateScaleChanges(deltaTime);
if (avatar->getTargetScale() <= MIN_FADE_SCALE) {
// fading to zero is such a rare event we push unique transaction for each one
// fading to zero is such a rare event we push a unique transaction for each
if (avatar->isInScene()) {
const render::ScenePointer& scene = qApp->getMain3DScene();
render::Transaction transaction;
avatar->removeFromScene(*itr, scene, transaction);
avatar->removeFromScene(*avatarItr, scene, transaction);
scene->enqueueTransaction(transaction);
}
// only remove from _avatarsToFade if we're sure its motionState has been removed from PhysicsEngine
if (_motionStatesToRemoveFromPhysics.empty()) {
itr = _avatarsToFade.erase(itr);
} else {
++itr;
}
avatarItr = _avatarsToFade.erase(avatarItr);
} else {
const bool inView = true; // HACK
avatar->simulate(deltaTime, inView);
++itr;
++avatarItr;
}
}
}
AvatarSharedPointer AvatarManager::newSharedAvatar() {
return std::make_shared<Avatar>(qApp->thread(), std::make_shared<Rig>());
return std::make_shared<OtherAvatar>(qApp->thread(), std::make_shared<Rig>());
}
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
AvatarHashMap::handleRemovedAvatar(removedAvatar, removalReason);
// removedAvatar is a shared pointer to an AvatarData but we need to get to the derived Avatar
// class in this context so we can call methods that don't exist at the base class.
// remove from physics
auto avatar = std::static_pointer_cast<Avatar>(removedAvatar);
AvatarMotionState* motionState = avatar->getMotionState();
if (motionState) {
_motionStatesThatMightUpdate.remove(motionState);
avatar->setPhysicsCallback(nullptr);
AvatarMotionStateMap::iterator itr = _motionStates.find(avatar.get());
if (itr != _motionStates.end()) {
AvatarMotionState* motionState = *itr;
_motionStatesToAddToPhysics.remove(motionState);
_motionStatesToRemoveFromPhysics.push_back(motionState);
_motionStates.erase(itr);
}
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
@ -373,11 +349,15 @@ void AvatarManager::clearOtherAvatars() {
++avatarIterator;
}
}
assert(scene);
scene->enqueueTransaction(transaction);
_myAvatar->clearLookAtTargetAvatar();
}
void AvatarManager::deleteAllAvatars() {
assert(_motionStates.empty()); // should have called clearOtherAvatars() before getting here
deleteMotionStates();
QReadLocker locker(&_hashLock);
AvatarHash::iterator avatarIterator = _avatarHash.begin();
while (avatarIterator != _avatarHash.end()) {
@ -387,27 +367,18 @@ void AvatarManager::deleteAllAvatars() {
}
}
void AvatarManager::setLocalLights(const QVector<AvatarManager::LocalLight>& localLights) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setLocalLights", Q_ARG(const QVector<AvatarManager::LocalLight>&, localLights));
return;
void AvatarManager::deleteMotionStates() {
// delete motionstates that were removed from physics last frame
for (auto state : _motionStatesToDelete) {
delete state;
}
_localLights = localLights;
}
QVector<AvatarManager::LocalLight> AvatarManager::getLocalLights() const {
if (QThread::currentThread() != thread()) {
QVector<AvatarManager::LocalLight> result;
QMetaObject::invokeMethod(const_cast<AvatarManager*>(this), "getLocalLights", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QVector<AvatarManager::LocalLight>, result));
return result;
}
return _localLights;
_motionStatesToDelete.clear();
}
void AvatarManager::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) {
result.clear();
result.swap(_motionStatesToRemoveFromPhysics);
deleteMotionStates();
result = _motionStatesToRemoveFromPhysics;
_motionStatesToDelete.swap(_motionStatesToRemoveFromPhysics);
}
void AvatarManager::getObjectsToAddToPhysics(VectorOfMotionStates& result) {
@ -420,10 +391,12 @@ void AvatarManager::getObjectsToAddToPhysics(VectorOfMotionStates& result) {
void AvatarManager::getObjectsToChange(VectorOfMotionStates& result) {
result.clear();
for (auto state : _motionStatesThatMightUpdate) {
if (state->_dirtyFlags > 0) {
result.push_back(state);
AvatarMotionStateMap::iterator motionStateItr = _motionStates.begin();
while (motionStateItr != _motionStates.end()) {
if ((*motionStateItr)->getIncomingDirtyFlags() != 0) {
result.push_back(*motionStateItr);
}
++motionStateItr;
}
}

View file

@ -21,13 +21,11 @@
#include <PIDController.h>
#include <SimpleMovingAverage.h>
#include <shared/RateCounter.h>
#include <avatars-renderer/AvatarMotionState.h>
#include <avatars-renderer/ScriptAvatar.h>
#include "Avatar.h"
#include "MyAvatar.h"
#include "AvatarMotionState.h"
#include "ScriptAvatar.h"
class MyAvatar;
class AudioInjector;
class AvatarManager : public AvatarHashMap {
@ -62,18 +60,6 @@ public:
void clearOtherAvatars();
void deleteAllAvatars();
bool shouldShowReceiveStats() const { return _shouldShowReceiveStats; }
class LocalLight {
public:
glm::vec3 color;
glm::vec3 direction;
};
Q_INVOKABLE void setLocalLights(const QVector<AvatarManager::LocalLight>& localLights);
Q_INVOKABLE QVector<AvatarManager::LocalLight> getLocalLights() const;
void getObjectsToRemoveFromPhysics(VectorOfMotionStates& motionStates);
void getObjectsToAddToPhysics(VectorOfMotionStates& motionStates);
void getObjectsToChange(VectorOfMotionStates& motionStates);
@ -95,7 +81,6 @@ public:
float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); }
public slots:
void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
void updateAvatarRenderStatus(bool shouldRenderAvatars);
private:
@ -105,21 +90,20 @@ private:
void simulateAvatarFades(float deltaTime);
AvatarSharedPointer newSharedAvatar() override;
void deleteMotionStates();
void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
QVector<AvatarSharedPointer> _avatarsToFade;
QSet<AvatarMotionState*> _motionStatesThatMightUpdate;
using AvatarMotionStateMap = QMap<Avatar*, AvatarMotionState*>;
AvatarMotionStateMap _motionStates;
VectorOfMotionStates _motionStatesToRemoveFromPhysics;
VectorOfMotionStates _motionStatesToDelete;
SetOfMotionStates _motionStatesToAddToPhysics;
std::shared_ptr<MyAvatar> _myAvatar;
quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate.
QVector<AvatarManager::LocalLight> _localLights;
bool _shouldShowReceiveStats = false;
std::list<QPointer<AudioInjector>> _collisionInjectors;
RateCounter<> _myAvatarSendRate;
@ -129,7 +113,4 @@ private:
bool _shouldRender { true };
};
Q_DECLARE_METATYPE(AvatarManager::LocalLight)
Q_DECLARE_METATYPE(QVector<AvatarManager::LocalLight>)
#endif // hifi_AvatarManager_h

274
interface/src/avatar/MyAvatar.cpp Normal file → Executable file
View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "MyAvatar.h"
#include <algorithm>
#include <vector>
@ -41,13 +43,14 @@
#include <recording/Clip.h>
#include <recording/Frame.h>
#include <RecordingScriptingInterface.h>
#include <trackers/FaceTracker.h>
#include "MyHead.h"
#include "MySkeletonModel.h"
#include "Application.h"
#include "devices/Faceshift.h"
#include "AvatarManager.h"
#include "AvatarActionHold.h"
#include "Menu.h"
#include "MyAvatar.h"
#include "Util.h"
#include "InterfaceLogging.h"
#include "DebugDraw.h"
@ -82,25 +85,26 @@ const float MyAvatar::ZOOM_MIN = 0.5f;
const float MyAvatar::ZOOM_MAX = 25.0f;
const float MyAvatar::ZOOM_DEFAULT = 1.5f;
// default values, used when avatar is missing joints... (avatar space)
// static const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 };
static const glm::vec3 DEFAULT_AVATAR_MIDDLE_EYE_POS { 0.0f, 0.6f, 0.0f };
static const glm::vec3 DEFAULT_AVATAR_HEAD_POS { 0.0f, 0.53f, 0.0f };
static const glm::vec3 DEFAULT_AVATAR_NECK_POS { 0.0f, 0.445f, 0.025f };
static const glm::vec3 DEFAULT_AVATAR_SPINE2_POS { 0.0f, 0.32f, 0.02f };
static const glm::vec3 DEFAULT_AVATAR_HIPS_POS { 0.0f, 0.0f, 0.0f };
static const glm::vec3 DEFAULT_AVATAR_LEFTFOOT_POS { -0.08f, -0.96f, 0.029f};
static const glm::quat DEFAULT_AVATAR_LEFTFOOT_ROT { -0.40167322754859924f, 0.9154590368270874f, -0.005437685176730156f, -0.023744143545627594f };
static const glm::vec3 DEFAULT_AVATAR_RIGHTFOOT_POS { 0.08f, -0.96f, 0.029f };
static const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { -0.4016716778278351f, 0.9154615998268127f, 0.0053307069465518f, 0.023696165531873703f };
MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
Avatar(thread, rig),
_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),
@ -117,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<MySkeletonModel>(this, nullptr, rig);
connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
using namespace recording;
_skeletonModel->flagAsCauterized();
@ -138,8 +150,6 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
// when we leave a domain we lift whatever restrictions that domain may have placed on our scale
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &MyAvatar::clearScaleRestriction);
_characterController.setEnabled(true);
_bodySensorMatrix = deriveBodyFromHMDSensor();
using namespace recording;
@ -153,12 +163,14 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
if (recordingInterface->getPlayFromCurrentLocation()) {
setRecordingBasis();
}
_wasCharacterControllerEnabled = _characterController.isEnabled();
_characterController.setEnabled(false);
_previousCollisionGroup = _characterController.computeCollisionGroup();
_characterController.setCollisionless(true);
} else {
clearRecordingBasis();
useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName);
_characterController.setEnabled(_wasCharacterControllerEnabled);
if (_previousCollisionGroup != BULLET_COLLISION_GROUP_COLLISIONLESS) {
_characterController.setCollisionless(false);
}
}
auto audioIO = DependencyManager::get<AudioClient>();
@ -524,7 +536,7 @@ void MyAvatar::simulate(float deltaTime) {
}
head->setPosition(headPosition);
head->setScale(getUniformScale());
head->simulate(deltaTime, true);
head->simulate(deltaTime);
}
// Record avatars movements.
@ -540,12 +552,12 @@ void MyAvatar::simulate(float deltaTime) {
EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
if (entityTree) {
bool flyingAllowed = true;
bool ghostingAllowed = true;
bool collisionlessAllowed = true;
entityTree->withWriteLock([&] {
std::shared_ptr<ZoneEntityItem> zone = entityTreeRenderer->myAvatarZone();
if (zone) {
flyingAllowed = zone->getFlyingAllowed();
ghostingAllowed = zone->getGhostingAllowed();
collisionlessAllowed = zone->getGhostingAllowed();
}
auto now = usecTimestampNow();
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
@ -576,9 +588,7 @@ void MyAvatar::simulate(float deltaTime) {
}
});
_characterController.setFlyingAllowed(flyingAllowed);
if (!_characterController.isEnabled() && !ghostingAllowed) {
_characterController.setEnabled(true);
}
_characterController.setCollisionlessAllowed(collisionlessAllowed);
}
updateAvatarEntities();
@ -650,18 +660,13 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
}
FaceTracker* tracker = qApp->getActiveFaceTracker();
bool inFacetracker = tracker && !tracker->isMuted();
bool inFacetracker = tracker && !FaceTracker::isMuted();
if (inHmd) {
estimatedPosition = extractTranslation(getHMDSensorMatrix());
estimatedPosition.x *= -1.0f;
_trackedHeadPosition = estimatedPosition;
const float OCULUS_LEAN_SCALE = 0.05f;
estimatedPosition /= OCULUS_LEAN_SCALE;
} else if (inFacetracker) {
estimatedPosition = tracker->getHeadTranslation();
_trackedHeadPosition = estimatedPosition;
estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation()));
}
@ -1378,18 +1383,78 @@ controller::Pose MyAvatar::getRightFootControllerPoseInAvatarFrame() const {
return getRightFootControllerPoseInWorldFrame().transform(invAvatarMatrix);
}
void MyAvatar::setSpineControllerPosesInSensorFrame(const controller::Pose& hips, const controller::Pose& spine2) {
if (controller::InputDevice::getLowVelocityFilter()) {
auto oldHipsPose = getHipsControllerPoseInSensorFrame();
auto oldSpine2Pose = getSpine2ControllerPoseInSensorFrame();
_hipsControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldHipsPose, hips));
_spine2ControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldSpine2Pose, spine2));
} else {
_hipsControllerPoseInSensorFrameCache.set(hips);
_spine2ControllerPoseInSensorFrameCache.set(spine2);
}
}
controller::Pose MyAvatar::getHipsControllerPoseInSensorFrame() const {
return _hipsControllerPoseInSensorFrameCache.get();
}
controller::Pose MyAvatar::getSpine2ControllerPoseInSensorFrame() const {
return _spine2ControllerPoseInSensorFrameCache.get();
}
controller::Pose MyAvatar::getHipsControllerPoseInWorldFrame() const {
return _hipsControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix());
}
controller::Pose MyAvatar::getSpine2ControllerPoseInWorldFrame() const {
return _spine2ControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix());
}
controller::Pose MyAvatar::getHipsControllerPoseInAvatarFrame() const {
glm::mat4 invAvatarMatrix = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition()));
return getHipsControllerPoseInWorldFrame().transform(invAvatarMatrix);
}
controller::Pose MyAvatar::getSpine2ControllerPoseInAvatarFrame() const {
glm::mat4 invAvatarMatrix = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition()));
return getSpine2ControllerPoseInWorldFrame().transform(invAvatarMatrix);
}
void MyAvatar::setHeadControllerPoseInSensorFrame(const controller::Pose& head) {
if (controller::InputDevice::getLowVelocityFilter()) {
auto oldHeadPose = getHeadControllerPoseInSensorFrame();
_headControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldHeadPose, head));
} else {
_headControllerPoseInSensorFrameCache.set(head);
}
}
controller::Pose MyAvatar::getHeadControllerPoseInSensorFrame() const {
return _headControllerPoseInSensorFrameCache.get();
}
controller::Pose MyAvatar::getHeadControllerPoseInWorldFrame() const {
return _headControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix());
}
controller::Pose MyAvatar::getHeadControllerPoseInAvatarFrame() const {
glm::mat4 invAvatarMatrix = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition()));
return getHeadControllerPoseInWorldFrame().transform(invAvatarMatrix);
}
void MyAvatar::updateMotors() {
_characterController.clearMotors();
glm::quat motorRotation;
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
if (_characterController.getState() == CharacterController::State::Hover) {
motorRotation = getHead()->getCameraOrientation();
if (_characterController.getState() == CharacterController::State::Hover ||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
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;
@ -1403,7 +1468,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 {
@ -1429,6 +1494,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
qDebug() << "Warning: getParentVelocity failed" << getID();
parentVelocity = glm::vec3();
}
_characterController.handleChangedCollisionGroup();
_characterController.setParentVelocity(parentVelocity);
_characterController.setPositionAndOrientation(getPosition(), getOrientation());
@ -1748,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.
@ -1817,8 +1883,9 @@ void MyAvatar::updateActionMotor(float deltaTime) {
glm::vec3 direction = forward + right;
CharacterController::State state = _characterController.getState();
if (state == CharacterController::State::Hover) {
// we're flying --> support vertical motion
if (state == CharacterController::State::Hover ||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
// we can fly --> support vertical motion
glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP;
direction += up;
}
@ -1840,7 +1907,7 @@ void MyAvatar::updateActionMotor(float deltaTime) {
float finalMaxMotorSpeed = getUniformScale() * MAX_ACTION_MOTOR_SPEED;
float speedGrowthTimescale = 2.0f;
float speedIncreaseFactor = 1.8f;
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor;
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale, 0.0f, 1.0f) * speedIncreaseFactor;
const float maxBoostSpeed = getUniformScale() * MAX_BOOST_SPEED;
if (_isPushing) {
@ -1883,16 +1950,24 @@ void MyAvatar::updatePosition(float deltaTime) {
measureMotionDerivatives(deltaTime);
_moving = speed2 > MOVING_SPEED_THRESHOLD_SQUARED;
} else {
// physics physics simulation updated elsewhere
float speed2 = glm::length2(velocity);
_moving = speed2 > MOVING_SPEED_THRESHOLD_SQUARED;
if (_moving) {
// scan for walkability
glm::vec3 position = getPosition();
MyCharacterController::RayShotgunResult result;
glm::vec3 step = deltaTime * (getRotation() * _actionMotorVelocity);
_characterController.testRayShotgun(position, step, result);
_characterController.setStepUpEnabled(result.walkable);
}
}
// capture the head rotation, in sensor space, when the user first indicates they would like to move/fly.
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;
}
@ -2122,30 +2197,33 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
} else {
_motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
}
setCharacterControllerEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController));
setCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions));
}
void MyAvatar::setCharacterControllerEnabled(bool enabled) {
void MyAvatar::setCollisionsEnabled(bool enabled) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setCharacterControllerEnabled", Q_ARG(bool, enabled));
QMetaObject::invokeMethod(this, "setCollisionsEnabled", Q_ARG(bool, enabled));
return;
}
bool ghostingAllowed = true;
auto entityTreeRenderer = qApp->getEntities();
if (entityTreeRenderer) {
std::shared_ptr<ZoneEntityItem> zone = entityTreeRenderer->myAvatarZone();
if (zone) {
ghostingAllowed = zone->getGhostingAllowed();
}
}
_characterController.setEnabled(ghostingAllowed ? enabled : true);
_characterController.setCollisionless(!enabled);
}
bool MyAvatar::getCollisionsEnabled() {
// may return 'false' even though the collisionless option was requested
// because the zone may disallow collisionless avatars
return _characterController.computeCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS;
}
void MyAvatar::setCharacterControllerEnabled(bool enabled) {
qCDebug(interfaceapp) << "MyAvatar.characterControllerEnabled is deprecated. Use MyAvatar.collisionsEnabled instead.";
setCollisionsEnabled(enabled);
}
bool MyAvatar::getCharacterControllerEnabled() {
return _characterController.isEnabled();
qCDebug(interfaceapp) << "MyAvatar.characterControllerEnabled is deprecated. Use MyAvatar.collisionsEnabled instead.";
return getCollisionsEnabled();
}
void MyAvatar::clearDriveKeys() {
@ -2220,22 +2298,17 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
const glm::quat hmdOrientation = getHMDSensorOrientation();
const glm::quat hmdOrientationYawOnly = cancelOutRollAndPitch(hmdOrientation);
// 2 meter tall dude (in rig coordinates)
const glm::vec3 DEFAULT_RIG_MIDDLE_EYE_POS(0.0f, 0.9f, 0.0f);
const glm::vec3 DEFAULT_RIG_NECK_POS(0.0f, 0.70f, 0.0f);
const glm::vec3 DEFAULT_RIG_HIPS_POS(0.0f, 0.05f, 0.0f);
int rightEyeIndex = _rig->indexOfJoint("RightEye");
int leftEyeIndex = _rig->indexOfJoint("LeftEye");
int neckIndex = _rig->indexOfJoint("Neck");
int hipsIndex = _rig->indexOfJoint("Hips");
glm::vec3 rigMiddleEyePos = DEFAULT_RIG_MIDDLE_EYE_POS;
glm::vec3 rigMiddleEyePos = DEFAULT_AVATAR_MIDDLE_EYE_POS;
if (leftEyeIndex >= 0 && rightEyeIndex >= 0) {
rigMiddleEyePos = (_rig->getAbsoluteDefaultPose(leftEyeIndex).trans() + _rig->getAbsoluteDefaultPose(rightEyeIndex).trans()) / 2.0f;
}
glm::vec3 rigNeckPos = neckIndex != -1 ? _rig->getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_RIG_NECK_POS;
glm::vec3 rigHipsPos = hipsIndex != -1 ? _rig->getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_RIG_HIPS_POS;
glm::vec3 rigNeckPos = neckIndex != -1 ? _rig->getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS;
glm::vec3 rigHipsPos = hipsIndex != -1 ? _rig->getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS;
glm::vec3 localEyes = (rigMiddleEyePos - rigHipsPos);
glm::vec3 localNeck = (rigNeckPos - rigHipsPos);
@ -2599,6 +2672,79 @@ glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
}
}
glm::mat4 MyAvatar::getCenterEyeCalibrationMat() const {
// TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed.
int rightEyeIndex = _rig->indexOfJoint("RightEye");
int leftEyeIndex = _rig->indexOfJoint("LeftEye");
if (rightEyeIndex >= 0 && leftEyeIndex >= 0) {
auto centerEyePos = (getAbsoluteDefaultJointTranslationInObjectFrame(rightEyeIndex) + getAbsoluteDefaultJointTranslationInObjectFrame(leftEyeIndex)) * 0.5f;
auto centerEyeRot = Quaternions::Y_180;
return createMatFromQuatAndPos(centerEyeRot, centerEyePos);
} else {
return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_POS, DEFAULT_AVATAR_MIDDLE_EYE_POS);
}
}
glm::mat4 MyAvatar::getHeadCalibrationMat() const {
// TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed.
int headIndex = _rig->indexOfJoint("Head");
if (headIndex >= 0) {
auto headPos = getAbsoluteDefaultJointTranslationInObjectFrame(headIndex);
auto headRot = getAbsoluteDefaultJointRotationInObjectFrame(headIndex);
return createMatFromQuatAndPos(headRot, headPos);
} else {
return createMatFromQuatAndPos(DEFAULT_AVATAR_HEAD_POS, DEFAULT_AVATAR_HEAD_POS);
}
}
glm::mat4 MyAvatar::getSpine2CalibrationMat() const {
// TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed.
int spine2Index = _rig->indexOfJoint("Spine2");
if (spine2Index >= 0) {
auto spine2Pos = getAbsoluteDefaultJointTranslationInObjectFrame(spine2Index);
auto spine2Rot = getAbsoluteDefaultJointRotationInObjectFrame(spine2Index);
return createMatFromQuatAndPos(spine2Rot, spine2Pos);
} else {
return createMatFromQuatAndPos(DEFAULT_AVATAR_SPINE2_POS, DEFAULT_AVATAR_SPINE2_POS);
}
}
glm::mat4 MyAvatar::getHipsCalibrationMat() const {
// TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed.
int hipsIndex = _rig->indexOfJoint("Hips");
if (hipsIndex >= 0) {
auto hipsPos = getAbsoluteDefaultJointTranslationInObjectFrame(hipsIndex);
auto hipsRot = getAbsoluteDefaultJointRotationInObjectFrame(hipsIndex);
return createMatFromQuatAndPos(hipsRot, hipsPos);
} else {
return createMatFromQuatAndPos(DEFAULT_AVATAR_HIPS_POS, DEFAULT_AVATAR_HIPS_POS);
}
}
glm::mat4 MyAvatar::getLeftFootCalibrationMat() const {
// TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed.
int leftFootIndex = _rig->indexOfJoint("LeftFoot");
if (leftFootIndex >= 0) {
auto leftFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(leftFootIndex);
auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex);
return createMatFromQuatAndPos(leftFootRot, leftFootPos);
} else {
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_POS, DEFAULT_AVATAR_LEFTFOOT_POS);
}
}
glm::mat4 MyAvatar::getRightFootCalibrationMat() const {
// TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed.
int rightFootIndex = _rig->indexOfJoint("RightFoot");
if (rightFootIndex >= 0) {
auto rightFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(rightFootIndex);
auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex);
return createMatFromQuatAndPos(rightFootRot, rightFootPos);
} else {
return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_POS, DEFAULT_AVATAR_RIGHTFOOT_POS);
}
}
bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& orientation) {
auto hipsIndex = getJointIndex("Hips");
if (index != hipsIndex) {
@ -2670,3 +2816,7 @@ void MyAvatar::updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose&
});
}
}
const MyHead* MyAvatar::getMyHead() const {
return static_cast<const MyHead*>(getHead());
}

View file

@ -22,14 +22,15 @@
#include <controllers/Pose.h>
#include <controllers/Actions.h>
#include <avatars-renderer/Avatar.h>
#include "Avatar.h"
#include "AtRestDetector.h"
#include "MyCharacterController.h"
#include <ThreadSafeValueCache.h>
class AvatarActionHold;
class ModelItemID;
class MyHead;
enum eyeContactTarget {
LEFT_EYE,
@ -95,7 +96,7 @@ class MyAvatar : public Avatar {
* @property rightHandTipPose {Pose} READ-ONLY. Returns a pose offset 30 cm from MyAvatar.rightHandPose
* @property hmdLeanRecenterEnabled {bool} This can be used disable the hmd lean recenter behavior. This behavior is what causes your avatar
* to follow your HMD as you walk around the room, in room scale VR. Disabling this is useful if you desire to pin the avatar to a fixed location.
* @property characterControllerEnabled {bool} This can be used to disable collisions between the avatar and the world.
* @property collisionsEnabled {bool} This can be used to disable collisions between the avatar and the world.
* @property useAdvancedMovementControls {bool} Stores the user preference only, does not change user mappings, this is done in the defaultScript
* "scripts/system/controllers/toggleAdvancedMovementForHandControllers.js".
*/
@ -127,6 +128,7 @@ class MyAvatar : public Avatar {
Q_PROPERTY(float isAway READ getIsAway WRITE setAway)
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
Q_PROPERTY(bool collisionsEnabled READ getCollisionsEnabled WRITE setCollisionsEnabled)
Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled)
Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls)
@ -149,6 +151,7 @@ public:
explicit MyAvatar(QThread* thread, RigPointer rig);
~MyAvatar();
void instantiableAvatar() override {};
void registerMetaTypes(QScriptEngine* engine);
virtual void simulateAttachments(float deltaTime) override;
@ -353,7 +356,7 @@ public:
eyeContactTarget getEyeContactTarget();
Q_INVOKABLE glm::vec3 getTrackedHeadPosition() const { return _trackedHeadPosition; }
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(); }
@ -453,14 +456,37 @@ public:
controller::Pose getLeftFootControllerPoseInAvatarFrame() const;
controller::Pose getRightFootControllerPoseInAvatarFrame() const;
void setSpineControllerPosesInSensorFrame(const controller::Pose& hips, const controller::Pose& spine2);
controller::Pose getHipsControllerPoseInSensorFrame() const;
controller::Pose getSpine2ControllerPoseInSensorFrame() const;
controller::Pose getHipsControllerPoseInWorldFrame() const;
controller::Pose getSpine2ControllerPoseInWorldFrame() const;
controller::Pose getHipsControllerPoseInAvatarFrame() const;
controller::Pose getSpine2ControllerPoseInAvatarFrame() const;
void setHeadControllerPoseInSensorFrame(const controller::Pose& head);
controller::Pose getHeadControllerPoseInSensorFrame() const;
controller::Pose getHeadControllerPoseInWorldFrame() const;
controller::Pose getHeadControllerPoseInAvatarFrame() const;
bool hasDriveInput() const;
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled);
Q_INVOKABLE bool getCharacterControllerEnabled();
Q_INVOKABLE void setCollisionsEnabled(bool enabled);
Q_INVOKABLE bool getCollisionsEnabled();
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); // deprecated
Q_INVOKABLE bool getCharacterControllerEnabled(); // deprecated
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
// all calibration matrices are in absolute avatar space.
glm::mat4 getCenterEyeCalibrationMat() const;
glm::mat4 getHeadCalibrationMat() const;
glm::mat4 getSpine2CalibrationMat() const;
glm::mat4 getHipsCalibrationMat() const;
glm::mat4 getLeftFootCalibrationMat() const;
glm::mat4 getRightFootCalibrationMat() const;
void addHoldAction(AvatarActionHold* holdAction); // thread-safe
void removeHoldAction(AvatarActionHold* holdAction); // thread-safe
void updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& postUpdatePose);
@ -569,17 +595,17 @@ private:
std::array<float, MAX_DRIVE_KEYS> _driveKeys;
std::bitset<MAX_DRIVE_KEYS> _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)
@ -591,11 +617,11 @@ private:
SharedSoundPointer _collisionSound;
MyCharacterController _characterController;
bool _wasCharacterControllerEnabled { true };
int16_t _previousCollisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR };
AvatarWeakPointer _lookAtTargetAvatar;
glm::vec3 _targetAvatarPosition;
bool _shouldRender;
bool _shouldRender { true };
float _oculusYawOffset;
eyeContactTarget _eyeContactTarget;
@ -693,9 +719,11 @@ private:
// These are stored in SENSOR frame
ThreadSafeValueCache<controller::Pose> _leftHandControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _rightHandControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _leftFootControllerPoseInSensorFrameCache{ controller::Pose() };
ThreadSafeValueCache<controller::Pose> _rightFootControllerPoseInSensorFrameCache{ controller::Pose() };
ThreadSafeValueCache<controller::Pose> _hipsControllerPoseInSensorFrameCache{ controller::Pose() };
ThreadSafeValueCache<controller::Pose> _spine2ControllerPoseInSensorFrameCache{ controller::Pose() };
ThreadSafeValueCache<controller::Pose> _headControllerPoseInSensorFrameCache{ controller::Pose() };
bool _hmdLeanRecenterEnabled = true;

286
interface/src/avatar/MyCharacterController.cpp Normal file → Executable file
View file

@ -15,11 +15,15 @@
#include "MyAvatar.h"
// TODO: improve walking up steps
// TODO: make avatars able to walk up and down steps/slopes
// TODO: make avatars stand on steep slope
// TODO: make avatars not snag on low ceilings
void MyCharacterController::RayShotgunResult::reset() {
hitFraction = 1.0f;
walkable = true;
}
MyCharacterController::MyCharacterController(MyAvatar* avatar) {
assert(avatar);
@ -30,37 +34,33 @@ MyCharacterController::MyCharacterController(MyAvatar* avatar) {
MyCharacterController::~MyCharacterController() {
}
void MyCharacterController::setDynamicsWorld(btDynamicsWorld* world) {
CharacterController::setDynamicsWorld(world);
if (world) {
initRayShotgun(world);
}
}
void MyCharacterController::updateShapeIfNecessary() {
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
_pendingFlags &= ~PENDING_FLAG_UPDATE_SHAPE;
// compute new dimensions from avatar's bounding box
float x = _boxScale.x;
float z = _boxScale.z;
_radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
_halfHeight = 0.5f * _boxScale.y - _radius;
float MIN_HALF_HEIGHT = 0.1f;
if (_halfHeight < MIN_HALF_HEIGHT) {
_halfHeight = MIN_HALF_HEIGHT;
}
// NOTE: _shapeLocalOffset is already computed
if (_radius > 0.0f) {
// create RigidBody if it doesn't exist
if (!_rigidBody) {
btCollisionShape* shape = computeShape();
// HACK: use some simple mass property defaults for now
const float DEFAULT_AVATAR_MASS = 100.0f;
const btScalar DEFAULT_AVATAR_MASS = 100.0f;
const btVector3 DEFAULT_AVATAR_INERTIA_TENSOR(30.0f, 8.0f, 30.0f);
btCollisionShape* shape = new btCapsuleShape(_radius, 2.0f * _halfHeight);
_rigidBody = new btRigidBody(DEFAULT_AVATAR_MASS, nullptr, shape, DEFAULT_AVATAR_INERTIA_TENSOR);
} else {
btCollisionShape* shape = _rigidBody->getCollisionShape();
if (shape) {
delete shape;
}
shape = new btCapsuleShape(_radius, 2.0f * _halfHeight);
shape = computeShape();
_rigidBody->setCollisionShape(shape);
}
@ -72,12 +72,262 @@ void MyCharacterController::updateShapeIfNecessary() {
if (_state == State::Hover) {
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
} else {
_rigidBody->setGravity(DEFAULT_CHARACTER_GRAVITY * _currentUp);
_rigidBody->setGravity(_gravity * _currentUp);
}
//_rigidBody->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
_rigidBody->setCollisionFlags(_rigidBody->getCollisionFlags() &
~(btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT));
} else {
// TODO: handle this failure case
}
}
}
bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm::vec3& step, RayShotgunResult& result) {
btVector3 rayDirection = glmToBullet(step);
btScalar stepLength = rayDirection.length();
if (stepLength < FLT_EPSILON) {
return false;
}
rayDirection /= stepLength;
// get _ghost ready for ray traces
btTransform transform = _rigidBody->getWorldTransform();
btVector3 newPosition = glmToBullet(position);
transform.setOrigin(newPosition);
_ghost.setWorldTransform(transform);
btMatrix3x3 rotation = transform.getBasis();
_ghost.refreshOverlappingPairCache();
CharacterRayResult rayResult(&_ghost);
CharacterRayResult closestRayResult(&_ghost);
btVector3 rayStart;
btVector3 rayEnd;
// compute rotation that will orient local ray start points to face step direction
btVector3 forward = rotation * btVector3(0.0f, 0.0f, -1.0f);
btVector3 adjustedDirection = rayDirection - rayDirection.dot(_currentUp) * _currentUp;
btVector3 axis = forward.cross(adjustedDirection);
btScalar lengthAxis = axis.length();
if (lengthAxis > FLT_EPSILON) {
// we're walking sideways
btScalar angle = acosf(lengthAxis / adjustedDirection.length());
if (rayDirection.dot(forward) < 0.0f) {
angle = PI - angle;
}
axis /= lengthAxis;
rotation = btMatrix3x3(btQuaternion(axis, angle)) * rotation;
} else if (rayDirection.dot(forward) < 0.0f) {
// we're walking backwards
rotation = btMatrix3x3(btQuaternion(_currentUp, PI)) * rotation;
}
// scan the top
// NOTE: if we scan an extra distance forward we can detect flat surfaces that are too steep to walk on.
// The approximate extra distance can be derived with trigonometry.
//
// minimumForward = [ (maxStepHeight + radius / cosTheta - radius) * (cosTheta / sinTheta) - radius ]
//
// where: theta = max angle between floor normal and vertical
//
// if stepLength is not long enough we can add the difference.
//
btScalar cosTheta = _minFloorNormalDotUp;
btScalar sinTheta = sqrtf(1.0f - cosTheta * cosTheta);
const btScalar MIN_FORWARD_SLOP = 0.12f; // HACK: not sure why this is necessary to detect steepest walkable slope
btScalar forwardSlop = (_maxStepHeight + _radius / cosTheta - _radius) * (cosTheta / sinTheta) - (_radius + stepLength) + MIN_FORWARD_SLOP;
if (forwardSlop < 0.0f) {
// BIG step, no slop necessary
forwardSlop = 0.0f;
}
const btScalar backSlop = 0.04f;
for (int32_t i = 0; i < _topPoints.size(); ++i) {
rayStart = newPosition + rotation * _topPoints[i] - backSlop * rayDirection;
rayEnd = rayStart + (backSlop + stepLength + forwardSlop) * rayDirection;
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
if (rayResult.m_closestHitFraction < closestRayResult.m_closestHitFraction) {
closestRayResult = rayResult;
}
if (result.walkable) {
if (rayResult.m_hitNormalWorld.dot(_currentUp) < _minFloorNormalDotUp) {
result.walkable = false;
// the top scan wasn't walkable so don't bother scanning the bottom
// remove both forwardSlop and backSlop
result.hitFraction = glm::min(1.0f, (closestRayResult.m_closestHitFraction * (backSlop + stepLength + forwardSlop) - backSlop) / stepLength);
return result.hitFraction < 1.0f;
}
}
}
}
if (_state == State::Hover) {
// scan the bottom just like the top
for (int32_t i = 0; i < _bottomPoints.size(); ++i) {
rayStart = newPosition + rotation * _bottomPoints[i] - backSlop * rayDirection;
rayEnd = rayStart + (backSlop + stepLength + forwardSlop) * rayDirection;
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
if (rayResult.m_closestHitFraction < closestRayResult.m_closestHitFraction) {
closestRayResult = rayResult;
}
if (result.walkable) {
if (rayResult.m_hitNormalWorld.dot(_currentUp) < _minFloorNormalDotUp) {
result.walkable = false;
// the bottom scan wasn't walkable
// remove both forwardSlop and backSlop
result.hitFraction = glm::min(1.0f, (closestRayResult.m_closestHitFraction * (backSlop + stepLength + forwardSlop) - backSlop) / stepLength);
return result.hitFraction < 1.0f;
}
}
}
}
} else {
// scan the bottom looking for nearest step point
// remove forwardSlop
result.hitFraction = (closestRayResult.m_closestHitFraction * (backSlop + stepLength + forwardSlop)) / (backSlop + stepLength);
for (int32_t i = 0; i < _bottomPoints.size(); ++i) {
rayStart = newPosition + rotation * _bottomPoints[i] - backSlop * rayDirection;
rayEnd = rayStart + (backSlop + stepLength) * rayDirection;
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
if (rayResult.m_closestHitFraction < closestRayResult.m_closestHitFraction) {
closestRayResult = rayResult;
}
}
}
// remove backSlop
// NOTE: backSlop removal can produce a NEGATIVE hitFraction!
// which means the shape is actually in interpenetration
result.hitFraction = ((closestRayResult.m_closestHitFraction * (backSlop + stepLength)) - backSlop) / stepLength;
}
return result.hitFraction < 1.0f;
}
btConvexHullShape* MyCharacterController::computeShape() const {
// HACK: the avatar collides using convex hull with a collision margin equal to
// the old capsule radius. Two points define a capsule and additional points are
// spread out at chest level to produce a slight taper toward the feet. This
// makes the avatar more likely to collide with vertical walls at a higher point
// and thus less likely to produce a single-point collision manifold below the
// _maxStepHeight when walking into against vertical surfaces --> fixes a bug
// where the "walk up steps" feature would allow the avatar to walk up vertical
// walls.
const int32_t NUM_POINTS = 6;
btVector3 points[NUM_POINTS];
btVector3 xAxis = btVector3(1.0f, 0.0f, 0.0f);
btVector3 yAxis = btVector3(0.0f, 1.0f, 0.0f);
btVector3 zAxis = btVector3(0.0f, 0.0f, 1.0f);
points[0] = _halfHeight * yAxis;
points[1] = -_halfHeight * yAxis;
points[2] = (0.75f * _halfHeight) * yAxis - (0.1f * _radius) * zAxis;
points[3] = (0.75f * _halfHeight) * yAxis + (0.1f * _radius) * zAxis;
points[4] = (0.75f * _halfHeight) * yAxis - (0.1f * _radius) * xAxis;
points[5] = (0.75f * _halfHeight) * yAxis + (0.1f * _radius) * xAxis;
btConvexHullShape* shape = new btConvexHullShape(reinterpret_cast<btScalar*>(points), NUM_POINTS);
shape->setMargin(_radius);
return shape;
}
void MyCharacterController::initRayShotgun(const btCollisionWorld* world) {
// In order to trace rays out from the avatar's shape surface we need to know where the start points are in
// the local-frame. Since the avatar shape is somewhat irregular computing these points by hand is a hassle
// so instead we ray-trace backwards to the avatar to find them.
//
// We trace back a regular grid (see below) of points against the shape and keep any that hit.
// ___
// + / + \ +
// |+ +|
// +| + | +
// |+ +|
// +| + | +
// |+ +|
// + \ + / +
// ---
// The shotgun will send rays out from these same points to see if the avatar's shape can proceed through space.
// helper class for simple ray-traces against character
class MeOnlyResultCallback : public btCollisionWorld::ClosestRayResultCallback {
public:
MeOnlyResultCallback (btRigidBody* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) {
_me = me;
m_collisionFilterGroup = BULLET_COLLISION_GROUP_DYNAMIC;
m_collisionFilterMask = BULLET_COLLISION_MASK_DYNAMIC;
}
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) override {
if (rayResult.m_collisionObject != _me) {
return 1.0f;
}
return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
}
btRigidBody* _me;
};
const btScalar fullHalfHeight = _radius + _halfHeight;
const btScalar divisionLine = -fullHalfHeight + _maxStepHeight; // line between top and bottom
const btScalar topHeight = fullHalfHeight - divisionLine;
const btScalar slop = 0.02f;
const int32_t NUM_ROWS = 5; // must be odd number > 1
const int32_t NUM_COLUMNS = 5; // must be odd number > 1
btVector3 reach = (2.0f * _radius) * btVector3(0.0f, 0.0f, 1.0f);
{ // top points
_topPoints.clear();
_topPoints.reserve(NUM_ROWS * NUM_COLUMNS);
btScalar stepY = (topHeight - slop) / (btScalar)(NUM_ROWS - 1);
btScalar stepX = 2.0f * (_radius - slop) / (btScalar)(NUM_COLUMNS - 1);
btTransform transform = _rigidBody->getWorldTransform();
btVector3 position = transform.getOrigin();
btMatrix3x3 rotation = transform.getBasis();
for (int32_t i = 0; i < NUM_ROWS; ++i) {
int32_t maxJ = NUM_COLUMNS;
btScalar offsetX = -(btScalar)((NUM_COLUMNS - 1) / 2) * stepX;
if (i % 2 == 1) {
// odd rows have one less point and start a halfStep closer
maxJ -= 1;
offsetX += 0.5f * stepX;
}
for (int32_t j = 0; j < maxJ; ++j) {
btVector3 localRayEnd(offsetX + (btScalar)(j) * stepX, divisionLine + (btScalar)(i) * stepY, 0.0f);
btVector3 localRayStart = localRayEnd - reach;
MeOnlyResultCallback result(_rigidBody);
world->rayTest(position + rotation * localRayStart, position + rotation * localRayEnd, result);
if (result.m_closestHitFraction < 1.0f) {
_topPoints.push_back(localRayStart + result.m_closestHitFraction * reach);
}
}
}
}
{ // bottom points
_bottomPoints.clear();
_bottomPoints.reserve(NUM_ROWS * NUM_COLUMNS);
btScalar steepestStepHitHeight = (_radius + 0.04f) * (1.0f - DEFAULT_MIN_FLOOR_NORMAL_DOT_UP);
btScalar stepY = (_maxStepHeight - slop - steepestStepHitHeight) / (btScalar)(NUM_ROWS - 1);
btScalar stepX = 2.0f * (_radius - slop) / (btScalar)(NUM_COLUMNS - 1);
btTransform transform = _rigidBody->getWorldTransform();
btVector3 position = transform.getOrigin();
btMatrix3x3 rotation = transform.getBasis();
for (int32_t i = 0; i < NUM_ROWS; ++i) {
int32_t maxJ = NUM_COLUMNS;
btScalar offsetX = -(btScalar)((NUM_COLUMNS - 1) / 2) * stepX;
if (i % 2 == 1) {
// odd rows have one less point and start a halfStep closer
maxJ -= 1;
offsetX += 0.5f * stepX;
}
for (int32_t j = 0; j < maxJ; ++j) {
btVector3 localRayEnd(offsetX + (btScalar)(j) * stepX, (divisionLine - slop) - (btScalar)(i) * stepY, 0.0f);
btVector3 localRayStart = localRayEnd - reach;
MeOnlyResultCallback result(_rigidBody);
world->rayTest(position + rotation * localRayStart, position + rotation * localRayEnd, result);
if (result.m_closestHitFraction < 1.0f) {
_bottomPoints.push_back(localRayStart + result.m_closestHitFraction * reach);
}
}
}
}
}

View file

@ -24,10 +24,34 @@ public:
explicit MyCharacterController(MyAvatar* avatar);
~MyCharacterController ();
virtual void updateShapeIfNecessary() override;
void setDynamicsWorld(btDynamicsWorld* world) override;
void updateShapeIfNecessary() override;
// Sweeping a convex shape through the physics simulation can be expensive when the obstacles are too
// complex (e.g. small 20k triangle static mesh) so instead we cast several rays forward and if they
// don't hit anything we consider it a clean sweep. Hence this "Shotgun" code.
class RayShotgunResult {
public:
void reset();
float hitFraction { 1.0f };
bool walkable { true };
};
/// return true if RayShotgun hits anything
bool testRayShotgun(const glm::vec3& position, const glm::vec3& step, RayShotgunResult& result);
protected:
void initRayShotgun(const btCollisionWorld* world);
private:
btConvexHullShape* computeShape() const;
protected:
MyAvatar* _avatar { nullptr };
// shotgun scan data
btAlignedObjectArray<btVector3> _topPoints;
btAlignedObjectArray<btVector3> _bottomPoints;
};
#endif // hifi_MyCharacterController_h

View file

@ -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 <glm/gtx/quaternion.hpp>
#include <gpu/Batch.h>
#include <NodeList.h>
#include <recording/Deck.h>
#include <Rig.h>
#include <trackers/FaceTracker.h>
#include <trackers/EyeTracker.h>
#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<MyAvatar*>(_owningAvatar);
return glm::quat_cast(myAvatar->getSensorToWorldMatrix()) * myAvatar->getHMDSensorOrientation();
} else {
Avatar* owningAvatar = static_cast<Avatar*>(_owningAvatar);
return owningAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f)));
}
}
void MyHead::simulate(float deltaTime) {
auto player = DependencyManager::get<recording::Deck>();
// 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<EyeTracker>();
_isEyeTrackerConnected = eyeTracker->isTracking();
}
Parent::simulate(deltaTime);
}

View file

@ -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 <avatars-renderer/Head.h>
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

View file

@ -0,0 +1,163 @@
//
// 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 <avatars-renderer/Avatar.h>
#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 = head->getLookAtPosition();
glm::vec3 focusOffset = lookAt - _owningAvatar->getHead()->getEyePosition();
float focusDistance = glm::length(focusOffset);
const float MIN_LOOK_AT_FOCUS_DISTANCE = 1.0f;
if (focusDistance < MIN_LOOK_AT_FOCUS_DISTANCE && focusDistance > EPSILON) {
lookAt = _owningAvatar->getHead()->getEyePosition() + (MIN_LOOK_AT_FOCUS_DISTANCE / focusDistance) * focusOffset;
}
MyAvatar* myAvatar = static_cast<MyAvatar*>(_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);
}

View file

@ -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 <avatars-renderer/SkeletonModel.h>
/// 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

View file

@ -9,20 +9,21 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "DdeFaceTracker.h"
#include <SharedUtil.h>
#include <QCoreApplication>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QTimer>
#include <QtCore/QCoreApplication>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include <QtCore/QTimer>
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <FaceshiftConstants.h>
#include "Application.h"
#include "DdeFaceTracker.h"
#include "FaceshiftConstants.h"
#include "InterfaceLogging.h"
#include "Menu.h"

View file

@ -12,6 +12,8 @@
#ifndef hifi_DdeFaceTracker_h
#define hifi_DdeFaceTracker_h
#include <QtCore/QtGlobal>
#if defined(Q_OS_WIN) || defined(Q_OS_OSX)
#define HAVE_DDE
#endif
@ -22,7 +24,7 @@
#include <DependencyManager.h>
#include <ui/overlays/TextOverlay.h>
#include "FaceTracker.h"
#include <trackers/FaceTracker.h>
class DdeFaceTracker : public FaceTracker, public Dependency {
Q_OBJECT

View file

@ -1,310 +0,0 @@
//
// Faceshift.cpp
// interface/src/devices
//
// Created by Andrzej Kapolka on 9/3/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QTimer>
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <PerfStat.h>
#include "Faceshift.h"
#include "Menu.h"
#include "Util.h"
#include "InterfaceLogging.h"
#ifdef HAVE_FACESHIFT
using namespace fs;
#endif
using namespace std;
const QString DEFAULT_FACESHIFT_HOSTNAME = "localhost";
const quint16 FACESHIFT_PORT = 33433;
Faceshift::Faceshift() :
_hostname("faceshiftHostname", DEFAULT_FACESHIFT_HOSTNAME)
{
#ifdef HAVE_FACESHIFT
connect(&_tcpSocket, SIGNAL(connected()), SLOT(noteConnected()));
connect(&_tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(noteError(QAbstractSocket::SocketError)));
connect(&_tcpSocket, SIGNAL(readyRead()), SLOT(readFromSocket()));
connect(&_tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SIGNAL(connectionStateChanged()));
connect(&_tcpSocket, SIGNAL(disconnected()), SLOT(noteDisconnected()));
connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams()));
_udpSocket.bind(FACESHIFT_PORT);
#endif
}
#ifdef HAVE_FACESHIFT
void Faceshift::init() {
FaceTracker::init();
setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) && !_isMuted);
}
void Faceshift::update(float deltaTime) {
if (!isActive()) {
return;
}
FaceTracker::update(deltaTime);
// get the euler angles relative to the window
glm::vec3 eulers = glm::degrees(safeEulerAngles(_headRotation * glm::quat(glm::radians(glm::vec3(
(_eyeGazeLeftPitch + _eyeGazeRightPitch) / 2.0f, (_eyeGazeLeftYaw + _eyeGazeRightYaw) / 2.0f, 0.0f)))));
// compute and subtract the long term average
const float LONG_TERM_AVERAGE_SMOOTHING = 0.999f;
if (!_longTermAverageInitialized) {
_longTermAverageEyePitch = eulers.x;
_longTermAverageEyeYaw = eulers.y;
_longTermAverageInitialized = true;
} else {
_longTermAverageEyePitch = glm::mix(eulers.x, _longTermAverageEyePitch, LONG_TERM_AVERAGE_SMOOTHING);
_longTermAverageEyeYaw = glm::mix(eulers.y, _longTermAverageEyeYaw, LONG_TERM_AVERAGE_SMOOTHING);
}
_estimatedEyePitch = eulers.x - _longTermAverageEyePitch;
_estimatedEyeYaw = eulers.y - _longTermAverageEyeYaw;
}
void Faceshift::reset() {
if (_tcpSocket.state() == QAbstractSocket::ConnectedState) {
qCDebug(interfaceapp, "Faceshift: Reset");
FaceTracker::reset();
string message;
fsBinaryStream::encode_message(message, fsMsgCalibrateNeutral());
send(message);
}
_longTermAverageInitialized = false;
}
bool Faceshift::isActive() const {
const quint64 ACTIVE_TIMEOUT_USECS = 1000000;
return (usecTimestampNow() - _lastReceiveTimestamp) < ACTIVE_TIMEOUT_USECS;
}
bool Faceshift::isTracking() const {
return isActive() && _tracking;
}
#endif
bool Faceshift::isConnectedOrConnecting() const {
return _tcpSocket.state() == QAbstractSocket::ConnectedState ||
(_tcpRetryCount == 0 && _tcpSocket.state() != QAbstractSocket::UnconnectedState);
}
void Faceshift::updateFakeCoefficients(float leftBlink, float rightBlink, float browUp,
float jawOpen, float mouth2, float mouth3, float mouth4, QVector<float>& coefficients) const {
const int MMMM_BLENDSHAPE = 34;
const int FUNNEL_BLENDSHAPE = 40;
const int SMILE_LEFT_BLENDSHAPE = 28;
const int SMILE_RIGHT_BLENDSHAPE = 29;
const int MAX_FAKE_BLENDSHAPE = 40; // Largest modified blendshape from above and below
coefficients.resize(max((int)coefficients.size(), MAX_FAKE_BLENDSHAPE + 1));
qFill(coefficients.begin(), coefficients.end(), 0.0f);
coefficients[_leftBlinkIndex] = leftBlink;
coefficients[_rightBlinkIndex] = rightBlink;
coefficients[_browUpCenterIndex] = browUp;
coefficients[_browUpLeftIndex] = browUp;
coefficients[_browUpRightIndex] = browUp;
coefficients[_jawOpenIndex] = jawOpen;
coefficients[SMILE_LEFT_BLENDSHAPE] = coefficients[SMILE_RIGHT_BLENDSHAPE] = mouth4;
coefficients[MMMM_BLENDSHAPE] = mouth2;
coefficients[FUNNEL_BLENDSHAPE] = mouth3;
}
void Faceshift::setEnabled(bool enabled) {
// Don't enable until have explicitly initialized
if (!_isInitialized) {
return;
}
#ifdef HAVE_FACESHIFT
if ((_tcpEnabled = enabled)) {
connectSocket();
} else {
qCDebug(interfaceapp, "Faceshift: Disconnecting...");
_tcpSocket.disconnectFromHost();
}
#endif
}
void Faceshift::connectSocket() {
if (_tcpEnabled) {
if (!_tcpRetryCount) {
qCDebug(interfaceapp, "Faceshift: Connecting...");
}
_tcpSocket.connectToHost(_hostname.get(), FACESHIFT_PORT);
_tracking = false;
}
}
void Faceshift::noteConnected() {
#ifdef HAVE_FACESHIFT
qCDebug(interfaceapp, "Faceshift: Connected");
// request the list of blendshape names
string message;
fsBinaryStream::encode_message(message, fsMsgSendBlendshapeNames());
send(message);
#endif
}
void Faceshift::noteDisconnected() {
#ifdef HAVE_FACESHIFT
qCDebug(interfaceapp, "Faceshift: Disconnected");
#endif
}
void Faceshift::noteError(QAbstractSocket::SocketError error) {
if (!_tcpRetryCount) {
// Only spam log with fail to connect the first time, so that we can keep waiting for server
qCWarning(interfaceapp) << "Faceshift: " << _tcpSocket.errorString();
}
// retry connection after a 2 second delay
if (_tcpEnabled) {
_tcpRetryCount++;
QTimer::singleShot(2000, this, SLOT(connectSocket()));
}
}
void Faceshift::readPendingDatagrams() {
QByteArray buffer;
while (_udpSocket.hasPendingDatagrams()) {
buffer.resize(_udpSocket.pendingDatagramSize());
_udpSocket.readDatagram(buffer.data(), buffer.size());
receive(buffer);
}
}
void Faceshift::readFromSocket() {
receive(_tcpSocket.readAll());
}
void Faceshift::send(const std::string& message) {
_tcpSocket.write(message.data(), message.size());
}
void Faceshift::receive(const QByteArray& buffer) {
#ifdef HAVE_FACESHIFT
_lastReceiveTimestamp = usecTimestampNow();
_stream.received(buffer.size(), buffer.constData());
fsMsgPtr msg;
for (fsMsgPtr msg; (msg = _stream.get_message()); ) {
switch (msg->id()) {
case fsMsg::MSG_OUT_TRACKING_STATE: {
const fsTrackingData& data = static_pointer_cast<fsMsgTrackingState>(msg)->tracking_data();
if ((_tracking = data.m_trackingSuccessful)) {
glm::quat newRotation = glm::quat(data.m_headRotation.w, -data.m_headRotation.x,
data.m_headRotation.y, -data.m_headRotation.z);
// Compute angular velocity of the head
glm::quat r = glm::normalize(newRotation * glm::inverse(_headRotation));
float theta = 2 * acos(r.w);
if (theta > EPSILON) {
float rMag = glm::length(glm::vec3(r.x, r.y, r.z));
_headAngularVelocity = theta / _averageFrameTime * glm::vec3(r.x, r.y, r.z) / rMag;
} else {
_headAngularVelocity = glm::vec3(0,0,0);
}
const float ANGULAR_VELOCITY_FILTER_STRENGTH = 0.3f;
_headRotation = safeMix(_headRotation, newRotation, glm::clamp(glm::length(_headAngularVelocity) *
ANGULAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f));
const float TRANSLATION_SCALE = 0.02f;
glm::vec3 newHeadTranslation = glm::vec3(data.m_headTranslation.x, data.m_headTranslation.y,
-data.m_headTranslation.z) * TRANSLATION_SCALE;
_headLinearVelocity = (newHeadTranslation - _lastHeadTranslation) / _averageFrameTime;
const float LINEAR_VELOCITY_FILTER_STRENGTH = 0.3f;
float velocityFilter = glm::clamp(1.0f - glm::length(_headLinearVelocity) *
LINEAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f);
_filteredHeadTranslation = velocityFilter * _filteredHeadTranslation + (1.0f - velocityFilter) * newHeadTranslation;
_lastHeadTranslation = newHeadTranslation;
_headTranslation = _filteredHeadTranslation;
_eyeGazeLeftPitch = -data.m_eyeGazeLeftPitch;
_eyeGazeLeftYaw = data.m_eyeGazeLeftYaw;
_eyeGazeRightPitch = -data.m_eyeGazeRightPitch;
_eyeGazeRightYaw = data.m_eyeGazeRightYaw;
_blendshapeCoefficients = QVector<float>::fromStdVector(data.m_coeffs);
const float FRAME_AVERAGING_FACTOR = 0.99f;
quint64 usecsNow = usecTimestampNow();
if (_lastMessageReceived != 0) {
_averageFrameTime = FRAME_AVERAGING_FACTOR * _averageFrameTime +
(1.0f - FRAME_AVERAGING_FACTOR) * (float)(usecsNow - _lastMessageReceived) / 1000000.0f;
}
_lastMessageReceived = usecsNow;
}
break;
}
case fsMsg::MSG_OUT_BLENDSHAPE_NAMES: {
const vector<string>& names = static_pointer_cast<fsMsgBlendshapeNames>(msg)->blendshape_names();
for (int i = 0; i < (int)names.size(); i++) {
if (names[i] == "EyeBlink_L") {
_leftBlinkIndex = i;
} else if (names[i] == "EyeBlink_R") {
_rightBlinkIndex = i;
} else if (names[i] == "EyeOpen_L") {
_leftEyeOpenIndex = i;
} else if (names[i] == "EyeOpen_R") {
_rightEyeOpenIndex = i;
} else if (names[i] == "BrowsD_L") {
_browDownLeftIndex = i;
} else if (names[i] == "BrowsD_R") {
_browDownRightIndex = i;
} else if (names[i] == "BrowsU_C") {
_browUpCenterIndex = i;
} else if (names[i] == "BrowsU_L") {
_browUpLeftIndex = i;
} else if (names[i] == "BrowsU_R") {
_browUpRightIndex = i;
} else if (names[i] == "JawOpen") {
_jawOpenIndex = i;
} else if (names[i] == "MouthSmile_L") {
_mouthSmileLeftIndex = i;
} else if (names[i] == "MouthSmile_R") {
_mouthSmileRightIndex = i;
}
}
break;
}
default:
break;
}
}
#endif
FaceTracker::countFrame();
}
void Faceshift::setHostname(const QString& hostname) {
_hostname.set(hostname);
}

View file

@ -1,155 +0,0 @@
//
// Faceshift.h
// interface/src/devices
//
// Created by Andrzej Kapolka on 9/3/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_Faceshift_h
#define hifi_Faceshift_h
#include <QTcpSocket>
#include <QUdpSocket>
#ifdef HAVE_FACESHIFT
#include <fsbinarystream.h>
#endif
#include <DependencyManager.h>
#include <SettingHandle.h>
#include "FaceTracker.h"
const float STARTING_FACESHIFT_FRAME_TIME = 0.033f;
/// Handles interaction with the Faceshift software, which provides head position/orientation and facial features.
class Faceshift : public FaceTracker, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public:
#ifdef HAVE_FACESHIFT
// If we don't have faceshift, use the base class' methods
virtual void init() override;
virtual void update(float deltaTime) override;
virtual void reset() override;
virtual bool isActive() const override;
virtual bool isTracking() const override;
#endif
bool isConnectedOrConnecting() const;
const glm::vec3& getHeadAngularVelocity() const { return _headAngularVelocity; }
// these pitch/yaw angles are in degrees
float getEyeGazeLeftPitch() const { return _eyeGazeLeftPitch; }
float getEyeGazeLeftYaw() const { return _eyeGazeLeftYaw; }
float getEyeGazeRightPitch() const { return _eyeGazeRightPitch; }
float getEyeGazeRightYaw() const { return _eyeGazeRightYaw; }
float getLeftBlink() const { return getBlendshapeCoefficient(_leftBlinkIndex); }
float getRightBlink() const { return getBlendshapeCoefficient(_rightBlinkIndex); }
float getLeftEyeOpen() const { return getBlendshapeCoefficient(_leftEyeOpenIndex); }
float getRightEyeOpen() const { return getBlendshapeCoefficient(_rightEyeOpenIndex); }
float getBrowDownLeft() const { return getBlendshapeCoefficient(_browDownLeftIndex); }
float getBrowDownRight() const { return getBlendshapeCoefficient(_browDownRightIndex); }
float getBrowUpCenter() const { return getBlendshapeCoefficient(_browUpCenterIndex); }
float getBrowUpLeft() const { return getBlendshapeCoefficient(_browUpLeftIndex); }
float getBrowUpRight() const { return getBlendshapeCoefficient(_browUpRightIndex); }
float getMouthSize() const { return getBlendshapeCoefficient(_jawOpenIndex); }
float getMouthSmileLeft() const { return getBlendshapeCoefficient(_mouthSmileLeftIndex); }
float getMouthSmileRight() const { return getBlendshapeCoefficient(_mouthSmileRightIndex); }
QString getHostname() { return _hostname.get(); }
void setHostname(const QString& hostname);
void updateFakeCoefficients(float leftBlink,
float rightBlink,
float browUp,
float jawOpen,
float mouth2,
float mouth3,
float mouth4,
QVector<float>& coefficients) const;
signals:
void connectionStateChanged();
public slots:
void setEnabled(bool enabled) override;
private slots:
void connectSocket();
void noteConnected();
void noteError(QAbstractSocket::SocketError error);
void readPendingDatagrams();
void readFromSocket();
void noteDisconnected();
private:
Faceshift();
virtual ~Faceshift() {}
void send(const std::string& message);
void receive(const QByteArray& buffer);
QTcpSocket _tcpSocket;
QUdpSocket _udpSocket;
#ifdef HAVE_FACESHIFT
fs::fsBinaryStream _stream;
#endif
bool _tcpEnabled = true;
int _tcpRetryCount = 0;
bool _tracking = false;
quint64 _lastReceiveTimestamp = 0;
quint64 _lastMessageReceived = 0;
float _averageFrameTime = STARTING_FACESHIFT_FRAME_TIME;
glm::vec3 _headAngularVelocity = glm::vec3(0.0f);
glm::vec3 _headLinearVelocity = glm::vec3(0.0f);
glm::vec3 _lastHeadTranslation = glm::vec3(0.0f);
glm::vec3 _filteredHeadTranslation = glm::vec3(0.0f);
// degrees
float _eyeGazeLeftPitch = 0.0f;
float _eyeGazeLeftYaw = 0.0f;
float _eyeGazeRightPitch = 0.0f;
float _eyeGazeRightYaw = 0.0f;
// degrees
float _longTermAverageEyePitch = 0.0f;
float _longTermAverageEyeYaw = 0.0f;
bool _longTermAverageInitialized = false;
Setting::Handle<QString> _hostname;
// see http://support.faceshift.com/support/articles/35129-export-of-blendshapes
int _leftBlinkIndex = 0;
int _rightBlinkIndex = 1;
int _leftEyeOpenIndex = 8;
int _rightEyeOpenIndex = 9;
// Brows
int _browDownLeftIndex = 14;
int _browDownRightIndex = 15;
int _browUpCenterIndex = 16;
int _browUpLeftIndex = 17;
int _browUpRightIndex = 18;
int _mouthSmileLeftIndex = 28;
int _mouthSmileRightIndex = 29;
int _jawOpenIndex = 21;
};
#endif // hifi_Faceshift_h

View file

@ -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 <NumericalConstants.h>
#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;

View file

@ -1,7 +1,4 @@
//
// Leapmotion.h
// interface/src/devices
//
// Created by Sam Cake on 6/2/2014
// Copyright 2014 High Fidelity, Inc.
//
@ -14,7 +11,7 @@
#include <QDateTime>
#include "MotionTracker.h"
#include <trackers/MotionTracker.h>
#ifdef HAVE_LEAPMOTION
#include <Leap.h>

View file

@ -17,7 +17,7 @@
#include <plugins/PluginManager.h>
#include "Application.h"
#include "devices/MotionTracker.h"
#include <trackers/MotionTracker.h>
void ControllerScriptingInterface::handleMetaEvent(HFMetaEvent* event) {
if (event->type() == HFActionEvent::startType()) {

View file

@ -11,9 +11,9 @@
#include <AudioClient.h>
#include <SettingHandle.h>
#include <trackers/FaceTracker.h>
#include "Application.h"
#include "devices/FaceTracker.h"
#include "Menu.h"
HIFI_QML_DEF(AvatarInputs)

View file

@ -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; }

View file

@ -28,11 +28,15 @@ const int MAX_HISTORY_SIZE = 64;
const QString COMMAND_STYLE = "color: #266a9b;";
const QString RESULT_SUCCESS_STYLE = "color: #677373;";
const QString RESULT_INFO_STYLE = "color: #223bd1;";
const QString RESULT_WARNING_STYLE = "color: #d13b22;";
const QString RESULT_ERROR_STYLE = "color: #d13b22;";
const QString GUTTER_PREVIOUS_COMMAND = "<span style=\"color: #57b8bb;\">&lt;</span>";
const QString GUTTER_ERROR = "<span style=\"color: #d13b22;\">X</span>";
const QString JSConsole::_consoleFileName { "about:console" };
JSConsole::JSConsole(QWidget* parent, ScriptEngine* scriptEngine) :
QWidget(parent),
_ui(new Ui::Console),
@ -77,6 +81,8 @@ void JSConsole::setScriptEngine(ScriptEngine* scriptEngine) {
}
if (_scriptEngine != NULL) {
disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &JSConsole::handlePrint);
disconnect(_scriptEngine, &ScriptEngine::infoMessage, this, &JSConsole::handleInfo);
disconnect(_scriptEngine, &ScriptEngine::warningMessage, this, &JSConsole::handleWarning);
disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &JSConsole::handleError);
if (_ownScriptEngine) {
_scriptEngine->deleteLater();
@ -84,10 +90,12 @@ void JSConsole::setScriptEngine(ScriptEngine* scriptEngine) {
}
// if scriptEngine is NULL then create one and keep track of it using _ownScriptEngine
_ownScriptEngine = scriptEngine == NULL;
_scriptEngine = _ownScriptEngine ? DependencyManager::get<ScriptEngines>()->loadScript(QString(), false) : scriptEngine;
_ownScriptEngine = (scriptEngine == NULL);
_scriptEngine = _ownScriptEngine ? DependencyManager::get<ScriptEngines>()->loadScript(_consoleFileName, false) : scriptEngine;
connect(_scriptEngine, &ScriptEngine::printedMessage, this, &JSConsole::handlePrint);
connect(_scriptEngine, &ScriptEngine::infoMessage, this, &JSConsole::handleInfo);
connect(_scriptEngine, &ScriptEngine::warningMessage, this, &JSConsole::handleWarning);
connect(_scriptEngine, &ScriptEngine::errorMessage, this, &JSConsole::handleError);
}
@ -107,11 +115,10 @@ void JSConsole::executeCommand(const QString& command) {
QScriptValue JSConsole::executeCommandInWatcher(const QString& command) {
QScriptValue result;
static const QString filename = "JSConcole";
QMetaObject::invokeMethod(_scriptEngine, "evaluate", Qt::ConnectionType::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, result),
Q_ARG(const QString&, command),
Q_ARG(const QString&, filename));
Q_ARG(const QString&, _consoleFileName));
return result;
}
@ -134,16 +141,26 @@ void JSConsole::commandFinished() {
resetCurrentCommandHistory();
}
void JSConsole::handleError(const QString& scriptName, const QString& message) {
void JSConsole::handleError(const QString& message, const QString& scriptName) {
Q_UNUSED(scriptName);
appendMessage(GUTTER_ERROR, "<span style='" + RESULT_ERROR_STYLE + "'>" + message.toHtmlEscaped() + "</span>");
}
void JSConsole::handlePrint(const QString& scriptName, const QString& message) {
void JSConsole::handlePrint(const QString& message, const QString& scriptName) {
Q_UNUSED(scriptName);
appendMessage("", message);
}
void JSConsole::handleInfo(const QString& message, const QString& scriptName) {
Q_UNUSED(scriptName);
appendMessage("", "<span style='" + RESULT_INFO_STYLE + "'>" + message.toHtmlEscaped() + "</span>");
}
void JSConsole::handleWarning(const QString& message, const QString& scriptName) {
Q_UNUSED(scriptName);
appendMessage("", "<span style='" + RESULT_WARNING_STYLE + "'>" + message.toHtmlEscaped() + "</span>");
}
void JSConsole::mouseReleaseEvent(QMouseEvent* event) {
_ui->promptTextEdit->setFocus();
}

View file

@ -47,8 +47,10 @@ protected:
protected slots:
void scrollToBottom();
void resizeTextInput();
void handlePrint(const QString& scriptName, const QString& message);
void handleError(const QString& scriptName, const QString& message);
void handlePrint(const QString& message, const QString& scriptName);
void handleInfo(const QString& message, const QString& scriptName);
void handleWarning(const QString& message, const QString& scriptName);
void handleError(const QString& message, const QString& scriptName);
void commandFinished();
private:
@ -66,6 +68,7 @@ private:
bool _ownScriptEngine;
QString _rootCommand;
ScriptEngine* _scriptEngine;
static const QString _consoleFileName;
};

View file

@ -11,7 +11,6 @@
#include <AudioClient.h>
#include <avatar/AvatarManager.h>
#include <devices/DdeFaceTracker.h>
#include <devices/Faceshift.h>
#include <NetworkingConstants.h>
#include <ScriptEngines.h>
#include <OffscreenUi.h>
@ -112,7 +111,7 @@ void setupPreferences() {
static const QString SNAPSHOTS { "Snapshots" };
{
auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); };
auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); };
auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); emit DependencyManager::get<Snapshot>()->snapshotLocationSet(value); };
auto preference = new BrowsePreference(SNAPSHOTS, "Put my snapshots here", getter, setter);
preferences->addPreference(preference);
}
@ -202,13 +201,6 @@ void setupPreferences() {
auto setter = [](float value) { FaceTracker::setEyeDeflection(value); };
preferences->addPreference(new SliderPreference(AVATAR_TUNING, "Face tracker eye deflection", getter, setter));
}
{
auto getter = []()->QString { return DependencyManager::get<Faceshift>()->getHostname(); };
auto setter = [](const QString& value) { DependencyManager::get<Faceshift>()->setHostname(value); };
auto preference = new EditPreference(AVATAR_TUNING, "Faceshift hostname", getter, setter);
preference->setPlaceholderText("localhost");
preferences->addPreference(preference);
}
{
auto getter = [=]()->QString { return myAvatar->getAnimGraphOverrideUrl().toString(); };
auto setter = [=](const QString& value) { myAvatar->setAnimGraphOverrideUrl(QUrl(value)); };
@ -338,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");

View file

@ -44,6 +44,9 @@ public:
static Setting::Handle<QString> snapshotsLocation;
static void uploadSnapshot(const QString& filename, const QUrl& href = QUrl(""));
signals:
void snapshotLocationSet(const QString& value);
public slots:
Q_INVOKABLE QString getSnapshotsLocation();
Q_INVOKABLE void setSnapshotsLocation(const QString& location);

View file

@ -31,6 +31,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
auto dataObject = doc.object().value("data").toObject();
QString thumbnailUrl = dataObject.value("thumbnail_url").toString();
QString imageUrl = dataObject.value("image_url").toString();
QString snapshotID = dataObject.value("id").toString();
auto addressManager = DependencyManager::get<AddressManager>();
QString placeName = _inWorldLocation.authority(); // We currently only upload shareable places, in which case this is just host.
QString currentPath = _inWorldLocation.path();
@ -43,6 +44,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
if (dataObject.contains("shareable_url")) {
detailsObject.insert("shareable_url", dataObject.value("shareable_url").toString());
}
detailsObject.insert("snapshot_id", snapshotID);
QString pickledDetails = QJsonDocument(detailsObject).toJson();
userStoryObject.insert("details", pickledDetails);
userStoryObject.insert("thumbnail_url", thumbnailUrl);

View file

@ -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());
}

View file

@ -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

View file

@ -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<Model>(std::make_shared<Rig>(), 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<QString>()) {
_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();
}

View file

@ -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 <typename itemType> using mapFunction = std::function<itemType(int jointIndex)>;
@ -55,6 +59,7 @@ private:
QUrl _url;
bool _updateModel = { false };
bool _scaleToFit = { false };
float _loadPriority { 0.0f };
};
#endif // hifi_ModelOverlay_h

View file

@ -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);

View file

@ -46,7 +46,6 @@ static bool isEqual(const glm::quat& p, const glm::quat& q) {
const glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 0.9f, 0.0f);
const glm::vec3 DEFAULT_LEFT_EYE_POS(0.3f, 0.9f, 0.0f);
const glm::vec3 DEFAULT_HEAD_POS(0.0f, 0.75f, 0.0f);
const glm::vec3 DEFAULT_NECK_POS(0.0f, 0.70f, 0.0f);
void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) {
@ -1020,7 +1019,7 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) {
}
void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
updateNeckJoint(params.neckJointIndex, params);
updateHeadAnimVars(params);
_animVars.set("isTalking", params.isTalking);
_animVars.set("notIsTalking", !params.isTalking);
@ -1028,101 +1027,73 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
if (params.hipsEnabled) {
_animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition);
_animVars.set("hipsPosition", extractTranslation(params.hipsMatrix));
_animVars.set("hipsRotation", glmExtractRotation(params.hipsMatrix) * Quaternions::Y_180);
_animVars.set("hipsRotation", glmExtractRotation(params.hipsMatrix));
} else {
_animVars.set("hipsType", (int)IKTarget::Type::Unknown);
}
// by default this IK target is disabled.
_animVars.set("spine2Type", (int)IKTarget::Type::Unknown);
if (params.spine2Enabled) {
_animVars.set("spine2Type", (int)IKTarget::Type::RotationAndPosition);
_animVars.set("spine2Position", extractTranslation(params.spine2Matrix));
_animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix));
} else {
_animVars.set("spine2Type", (int)IKTarget::Type::Unknown);
}
}
void Rig::updateFromEyeParameters(const EyeParameters& params) {
updateEyeJoint(params.leftEyeJointIndex, params.modelTranslation, params.modelRotation,
params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade);
updateEyeJoint(params.rightEyeJointIndex, params.modelTranslation, params.modelRotation,
params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade);
updateEyeJoint(params.leftEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade);
updateEyeJoint(params.rightEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade);
}
void Rig::computeHeadNeckAnimVars(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut,
glm::vec3& neckPositionOut, glm::quat& neckOrientationOut) const {
void Rig::computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const {
// the input hmd values are in avatar/rig space
const glm::vec3& hmdPosition = hmdPose.trans();
const glm::quat& hmdOrientation = hmdPose.rot();
// the HMD looks down the negative z axis, but the head bone looks down the z axis, so apply a 180 degree rotation.
const glm::quat& hmdOrientation = hmdPose.rot() * Quaternions::Y_180;
// TODO: cache jointIndices
int rightEyeIndex = indexOfJoint("RightEye");
int leftEyeIndex = indexOfJoint("LeftEye");
int headIndex = indexOfJoint("Head");
int neckIndex = indexOfJoint("Neck");
glm::vec3 absRightEyePos = rightEyeIndex != -1 ? getAbsoluteDefaultPose(rightEyeIndex).trans() : DEFAULT_RIGHT_EYE_POS;
glm::vec3 absLeftEyePos = leftEyeIndex != -1 ? getAbsoluteDefaultPose(leftEyeIndex).trans() : DEFAULT_LEFT_EYE_POS;
glm::vec3 absHeadPos = headIndex != -1 ? getAbsoluteDefaultPose(headIndex).trans() : DEFAULT_HEAD_POS;
glm::vec3 absNeckPos = neckIndex != -1 ? getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_NECK_POS;
glm::vec3 absCenterEyePos = (absRightEyePos + absLeftEyePos) / 2.0f;
glm::vec3 eyeOffset = absCenterEyePos - absHeadPos;
glm::vec3 headOffset = absHeadPos - absNeckPos;
// apply simplistic head/neck model
// head
headPositionOut = hmdPosition - hmdOrientation * eyeOffset;
headOrientationOut = hmdOrientation;
// neck
neckPositionOut = hmdPosition - hmdOrientation * (headOffset + eyeOffset);
// slerp between default orientation and hmdOrientation
neckOrientationOut = safeMix(hmdOrientation, _animSkeleton->getRelativeDefaultPose(neckIndex).rot(), 0.5f);
}
void Rig::updateNeckJoint(int index, const HeadParameters& params) {
if (_animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints()) {
glm::quat yFlip180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
if (params.isInHMD) {
glm::vec3 headPos, neckPos;
glm::quat headRot, neckRot;
AnimPose hmdPose(glm::vec3(1.0f), params.rigHeadOrientation * yFlip180, params.rigHeadPosition);
computeHeadNeckAnimVars(hmdPose, headPos, headRot, neckPos, neckRot);
// debug rendering
#ifdef DEBUG_RENDERING
const glm::vec4 red(1.0f, 0.0f, 0.0f, 1.0f);
const glm::vec4 green(0.0f, 1.0f, 0.0f, 1.0f);
// transform from bone into avatar space
AnimPose headPose(glm::vec3(1), headRot, headPos);
DebugDraw::getInstance().addMyAvatarMarker("headTarget", headPose.rot, headPose.trans, red);
// transform from bone into avatar space
AnimPose neckPose(glm::vec3(1), neckRot, neckPos);
DebugDraw::getInstance().addMyAvatarMarker("neckTarget", neckPose.rot, neckPose.trans, green);
#endif
_animVars.set("headPosition", headPos);
_animVars.set("headRotation", headRot);
_animVars.set("headType", (int)IKTarget::Type::HmdHead);
_animVars.set("neckPosition", neckPos);
_animVars.set("neckRotation", neckRot);
_animVars.set("neckType", (int)IKTarget::Type::Unknown); // 'Unknown' disables the target
void Rig::updateHeadAnimVars(const HeadParameters& params) {
if (_animSkeleton) {
if (params.headEnabled) {
_animVars.set("headPosition", params.rigHeadPosition);
_animVars.set("headRotation", params.rigHeadOrientation);
if (params.hipsEnabled) {
// Since there is an explicit hips ik target, switch the head to use the more generic RotationAndPosition IK chain type.
// this will allow the spine to bend more, ensuring that it can reach the head target position.
_animVars.set("headType", (int)IKTarget::Type::RotationAndPosition);
} else {
// When there is no hips IK target, use the HmdHead IK chain type. This will make the spine very stiff,
// but because the IK _hipsOffset is enabled, the hips will naturally follow underneath the head.
_animVars.set("headType", (int)IKTarget::Type::HmdHead);
}
} else {
_animVars.unset("headPosition");
_animVars.set("headRotation", params.rigHeadOrientation * yFlip180);
_animVars.set("headAndNeckType", (int)IKTarget::Type::RotationOnly);
_animVars.set("headRotation", params.rigHeadOrientation);
_animVars.set("headType", (int)IKTarget::Type::RotationOnly);
_animVars.unset("neckPosition");
_animVars.unset("neckRotation");
_animVars.set("neckType", (int)IKTarget::Type::RotationOnly);
}
}
}
void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) {
void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) {
// TODO: does not properly handle avatar scale.

View file

@ -42,18 +42,17 @@ public:
};
struct HeadParameters {
glm::quat worldHeadOrientation = glm::quat(); // world space (-z forward)
glm::quat rigHeadOrientation = glm::quat(); // rig space (-z forward)
glm::vec3 rigHeadPosition = glm::vec3(); // rig space
glm::mat4 hipsMatrix = glm::mat4(); // rig space
glm::mat4 hipsMatrix = glm::mat4(); // rig space
glm::mat4 spine2Matrix = glm::mat4(); // rig space
glm::quat rigHeadOrientation = glm::quat(); // rig space (-z forward)
glm::vec3 rigHeadPosition = glm::vec3(); // rig space
bool hipsEnabled = false;
bool isInHMD = false;
int neckJointIndex = -1;
bool headEnabled = false;
bool spine2Enabled = false;
bool isTalking = false;
};
struct EyeParameters {
glm::quat worldHeadOrientation = glm::quat();
glm::vec3 eyeLookAt = glm::vec3(); // world space
glm::vec3 eyeSaccade = glm::vec3(); // world space
glm::vec3 modelTranslation = glm::vec3();
@ -230,6 +229,9 @@ public:
void setEnableDebugDrawIKTargets(bool enableDebugDrawIKTargets) { _enableDebugDrawIKTargets = enableDebugDrawIKTargets; }
// input assumed to be in rig space
void computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const;
signals:
void onLoadComplete();
@ -239,10 +241,9 @@ protected:
void applyOverridePoses();
void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut);
void updateNeckJoint(int index, const HeadParameters& params);
void computeHeadNeckAnimVars(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut,
glm::vec3& neckPositionOut, glm::quat& neckOrientationOut) const;
void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade);
void updateHeadAnimVars(const HeadParameters& params);
void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade);
void calcAnimAlpha(float speed, const std::vector<float>& referenceSpeeds, float* alphaOut) const;
AnimPose _modelOffset; // model to rig space

View file

@ -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<int>::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);

View file

@ -96,8 +96,6 @@ public:
using AudioPositionGetter = std::function<glm::vec3()>;
using AudioOrientationGetter = std::function<glm::quat()>;
using RecursiveMutex = std::recursive_mutex;
using RecursiveLock = std::unique_lock<RecursiveMutex>;
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
@ -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 };

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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),

View file

@ -19,7 +19,7 @@
class AudioInjectorLocalBuffer : public QIODevice {
Q_OBJECT
public:
AudioInjectorLocalBuffer(const QByteArray& rawAudioArray, QObject* parent);
AudioInjectorLocalBuffer(const QByteArray& rawAudioArray);
void stop();

View file

@ -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()

View file

@ -27,18 +27,13 @@
#include <TextRenderer3D.h>
#include <VariantMapToScriptValue.h>
#include <DebugDraw.h>
#include <shared/Camera.h>
#include <SoftAttachmentModel.h>
#include "AvatarManager.h"
#include "AvatarMotionState.h"
#include "Camera.h"
#include "Menu.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;
@ -59,7 +54,6 @@ namespace render {
auto avatarPtr = static_pointer_cast<Avatar>(avatar);
if (avatarPtr->isInitialized() && args) {
PROFILE_RANGE_BATCH(*args->_batch, "renderAvatarPayload");
// TODO AVATARS_RENDERER: remove need for qApp
avatarPtr->render(args);
}
}
@ -74,26 +68,37 @@ namespace render {
}
}
bool showAvatars { true };
void Avatar::setShowAvatars(bool render) {
showAvatars = render;
}
static bool showReceiveStats = false;
void Avatar::setShowReceiveStats(bool receiveStats) {
showReceiveStats = receiveStats;
}
static bool showMyLookAtVectors = false;
void Avatar::setShowMyLookAtVectors(bool showMine) {
showMyLookAtVectors = showMine;
}
static bool showOtherLookAtVectors = false;
void Avatar::setShowOtherLookAtVectors(bool showOthers) {
showOtherLookAtVectors = showOthers;
}
static bool showCollisionShapes = false;
void Avatar::setShowCollisionShapes(bool render) {
showCollisionShapes = render;
}
static bool showNamesAboveHeads = false;
void Avatar::setShowNamesAboveHeads(bool show) {
showNamesAboveHeads = 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<float>::max()),
_smoothOrientationTime(SMOOTH_TIME_ORIENTATION),
_smoothOrientationTimer(std::numeric_limits<float>::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
@ -101,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<HeadData*>(new Head(this));
_skeletonModel = std::make_shared<SkeletonModel>(this, nullptr, rig);
connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
auto geometryCache = DependencyManager::get<GeometryCache>();
_nameRectGeometryID = geometryCache->allocateID();
_leftPointerGeometryID = geometryCache->allocateID();
@ -126,11 +125,6 @@ Avatar::~Avatar() {
});
}
if (_motionState) {
delete _motionState;
_motionState = nullptr;
}
auto geometryCache = DependencyManager::get<GeometryCache>();
if (geometryCache) {
geometryCache->releaseID(_nameRectGeometryID);
@ -266,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;
}
@ -289,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);
}
@ -350,7 +344,7 @@ void Avatar::simulate(float deltaTime, bool inView) {
_smoothPositionTimer += deltaTime;
if (_smoothPositionTimer < _smoothPositionTime) {
AvatarData::setPosition(
lerp(_smoothPositionInitial,
lerp(_smoothPositionInitial,
_smoothPositionTarget,
easeInOutQuad(glm::clamp(_smoothPositionTimer / _smoothPositionTime, 0.0f, 1.0f)))
);
@ -363,7 +357,7 @@ void Avatar::simulate(float deltaTime, bool inView) {
_smoothOrientationTimer += deltaTime;
if (_smoothOrientationTimer < _smoothOrientationTime) {
AvatarData::setOrientation(
slerp(_smoothOrientationInitial,
slerp(_smoothOrientationInitial,
_smoothOrientationTarget,
easeInOutQuad(glm::clamp(_smoothOrientationTimer / _smoothOrientationTime, 0.0f, 1.0f)))
);
@ -393,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);
@ -508,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<AvatarData>(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);
}
}
@ -535,14 +530,7 @@ void Avatar::updateRenderItem(render::Transaction& transaction) {
void Avatar::postUpdate(float deltaTime) {
bool renderLookAtVectors;
if (isMyAvatar()) {
renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderMyLookAtVectors);
} else {
renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderOtherLookAtVectors);
}
if (renderLookAtVectors) {
if (isMyAvatar() ? showMyLookAtVectors : showOtherLookAtVectors) {
const float EYE_RAY_LENGTH = 10.0;
const glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f);
const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
@ -583,7 +571,6 @@ void Avatar::render(RenderArgs* renderArgs) {
bool havePosition, haveRotation;
if (_handState & LEFT_HAND_POINTING_FLAG) {
if (_handState & IS_FINGER_POINTING_FLAG) {
int leftIndexTip = getJointIndex("LeftHandIndex4");
int leftIndexTipJoint = getJointIndex("LeftHandIndex3");
@ -636,44 +623,25 @@ void Avatar::render(RenderArgs* renderArgs) {
return;
}
glm::vec3 toTarget = frustum.getPosition() - getPosition();
float distanceToTarget = glm::length(toTarget);
fixupModelsInScene(renderArgs->_scene);
{
fixupModelsInScene(renderArgs->_scene);
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE) {
// add local lights
const float BASE_LIGHT_DISTANCE = 2.0f;
const float LIGHT_FALLOFF_RADIUS = 0.01f;
const float LIGHT_EXPONENT = 1.0f;
const float LIGHT_CUTOFF = glm::radians(80.0f);
float distance = BASE_LIGHT_DISTANCE * getUniformScale();
glm::vec3 position = _skeletonModel->getTranslation();
glm::quat orientation = getOrientation();
foreach (const AvatarManager::LocalLight& light, DependencyManager::get<AvatarManager>()->getLocalLights()) {
glm::vec3 direction = orientation * light.direction;
DependencyManager::get<DeferredLightingEffect>()->addSpotLight(position - direction * distance,
distance * 2.0f, light.color, 0.5f, LIGHT_FALLOFF_RADIUS, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF);
}
}
bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
if (renderBounding && shouldRenderHead(renderArgs) && _skeletonModel->isRenderable()) {
PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes");
const float BOUNDING_SHAPE_ALPHA = 0.7f;
_skeletonModel->renderBoundingCollisionShapes(*renderArgs->_batch, getUniformScale(), BOUNDING_SHAPE_ALPHA);
}
if (showCollisionShapes && shouldRenderHead(renderArgs) && _skeletonModel->isRenderable()) {
PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes");
const float BOUNDING_SHAPE_ALPHA = 0.7f;
_skeletonModel->renderBoundingCollisionShapes(*renderArgs->_batch, getUniformScale(), BOUNDING_SHAPE_ALPHA);
}
const float DISPLAYNAME_DISTANCE = 20.0f;
setShowDisplayName(distanceToTarget < DISPLAYNAME_DISTANCE);
if (!isMyAvatar() || renderArgs->_cameraMode != (int8_t)CAMERA_MODE_FIRST_PERSON) {
auto& frustum = renderArgs->getViewFrustum();
auto textPosition = getDisplayNamePosition();
if (frustum.pointIntersectsFrustum(textPosition)) {
renderDisplayName(batch, frustum, textPosition);
if (showReceiveStats || showNamesAboveHeads) {
glm::vec3 toTarget = frustum.getPosition() - getPosition();
float distanceToTarget = glm::length(toTarget);
const float DISPLAYNAME_DISTANCE = 20.0f;
updateDisplayNameAlpha(distanceToTarget < DISPLAYNAME_DISTANCE);
if (!isMyAvatar() || renderArgs->_cameraMode != (int8_t)CAMERA_MODE_FIRST_PERSON) {
auto& frustum = renderArgs->getViewFrustum();
auto textPosition = getDisplayNamePosition();
if (frustum.pointIntersectsFrustum(textPosition)) {
renderDisplayName(batch, frustum, textPosition);
}
}
}
}
@ -758,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)
@ -793,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);
}
@ -829,7 +797,7 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& view, const g
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const {
PROFILE_RANGE_BATCH(batch, __FUNCTION__);
bool shouldShowReceiveStats = DependencyManager::get<AvatarManager>()->shouldShowReceiveStats() && !isMyAvatar();
bool shouldShowReceiveStats = showReceiveStats && !isMyAvatar();
// If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return
static const float CLIP_DISTANCE = 0.2f;
@ -1125,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;
}
@ -1200,8 +1168,8 @@ int Avatar::parseDataFromBuffer(const QByteArray& buffer) {
const float MOVE_DISTANCE_THRESHOLD = 0.001f;
_moving = glm::distance(oldPosition, getPosition()) > MOVE_DISTANCE_THRESHOLD;
if (_moving && _motionState) {
_motionState->addDirtyFlags(Simulation::DIRTY_POSITION);
if (_moving) {
addPhysicsFlags(Simulation::DIRTY_POSITION);
}
if (_moving || _hasNewJointData) {
locationChanged();
@ -1284,8 +1252,8 @@ float Avatar::getPelvisFloatingHeight() const {
return -_skeletonModel->getBindExtents().minimum.y;
}
void Avatar::setShowDisplayName(bool showDisplayName) {
if (!Menu::getInstance()->isOptionChecked(MenuOption::NamesAboveHeads)) {
void Avatar::updateDisplayNameAlpha(bool showDisplayName) {
if (!(showNamesAboveHeads || showReceiveStats)) {
_displayNameAlpha = 0.0f;
return;
}
@ -1323,14 +1291,18 @@ void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) {
radius = halfExtents.x;
}
void Avatar::setMotionState(AvatarMotionState* motionState) {
_motionState = motionState;
}
// virtual
void Avatar::rebuildCollisionShape() {
if (_motionState) {
_motionState->addDirtyFlags(Simulation::DIRTY_SHAPE);
addPhysicsFlags(Simulation::DIRTY_SHAPE);
}
void Avatar::setPhysicsCallback(AvatarPhysicsCallback cb) {
_physicsCallback = cb;
}
void Avatar::addPhysicsFlags(uint32_t flags) {
if (_physicsCallback) {
_physicsCallback(flags);
}
}
@ -1444,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();
@ -1462,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.";
}
}
}
@ -1493,16 +1465,16 @@ QList<QVariant> Avatar::getSkeleton() {
void Avatar::addToScene(AvatarSharedPointer myHandle, const render::ScenePointer& scene) {
if (scene) {
render::Transaction transaction;
auto nodelist = DependencyManager::get<NodeList>();
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars()
if (showAvatars
&& !nodelist->isIgnoringNode(getSessionUUID())
&& !nodelist->isRadiusIgnoringNode(getSessionUUID())) {
render::Transaction transaction;
addToScene(myHandle, scene, transaction);
scene->enqueueTransaction(transaction);
}
scene->enqueueTransaction(transaction);
} else {
qCWarning(interfaceapp) << "AvatarManager::addAvatar() : Unexpected null scene, possibly during application shutdown";
qCWarning(avatars_renderer) << "Avatar::addAvatar() : Unexpected null scene, possibly during application shutdown";
}
}

View file

@ -11,6 +11,7 @@
#ifndef hifi_Avatar_h
#define hifi_Avatar_h
#include <functional>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
@ -19,6 +20,7 @@
#include <AvatarData.h>
#include <ShapeInfo.h>
#include <render/Scene.h>
#include <GLMHelpers.h>
#include "Head.h"
@ -48,9 +50,10 @@ enum ScreenTintLayer {
NUM_SCREEN_TINT_LAYERS
};
class AvatarMotionState;
class Texture;
using AvatarPhysicsCallback = std::function<void(uint32_t)>;
class Avatar : public AvatarData {
Q_OBJECT
@ -66,9 +69,18 @@ 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);
static void setShowCollisionShapes(bool render);
static void setShowNamesAboveHeads(bool show);
explicit Avatar(QThread* thread, RigPointer rig = nullptr);
~Avatar();
virtual void instantiableAvatar() = 0;
typedef render::Payload<AvatarData> Payload;
typedef std::shared_ptr<render::Item::PayloadInterface> PayloadPointer;
@ -148,7 +160,7 @@ public:
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData) override;
void setShowDisplayName(bool showDisplayName);
void updateDisplayNameAlpha(bool showDisplayName);
virtual void setSessionDisplayName(const QString& sessionDisplayName) override { }; // no-op
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
@ -185,8 +197,6 @@ public:
virtual void computeShapeInfo(ShapeInfo& shapeInfo);
void getCapsule(glm::vec3& start, glm::vec3& end, float& radius);
AvatarMotionState* getMotionState() { return _motionState; }
using SpatiallyNestable::setPosition;
virtual void setPosition(const glm::vec3& position) override;
using SpatiallyNestable::setOrientation;
@ -238,6 +248,16 @@ public:
return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f);
}
float getBoundingRadius() const;
void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene);
void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene);
bool isInScene() const { return render::Item::isValidID(_renderItemID); }
bool isMoving() const { return _moving; }
void setPhysicsCallback(AvatarPhysicsCallback cb);
void addPhysicsFlags(uint32_t flags);
bool isInPhysicsSimulation() const { return _physicsCallback != nullptr; }
public slots:
@ -251,8 +271,6 @@ public slots:
void setModelURLFinished(bool success);
protected:
friend class AvatarManager;
const float SMOOTH_TIME_POSITION = 0.125f;
const float SMOOTH_TIME_ORIENTATION = 0.075f;
@ -260,15 +278,13 @@ protected:
QString _empty{};
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter!
void setMotionState(AvatarMotionState* motionState);
SkeletonModelPointer _skeletonModel;
glm::vec3 _skeletonOffset;
std::vector<std::shared_ptr<Model>> _attachmentModels;
std::vector<std::shared_ptr<Model>> _attachmentsToRemove;
std::vector<std::shared_ptr<Model>> _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
@ -284,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;
@ -315,10 +330,6 @@ protected:
ThreadSafeValueCache<glm::vec3> _rightPalmPositionCache { glm::vec3() };
ThreadSafeValueCache<glm::quat> _rightPalmRotationCache { glm::quat() };
void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene);
void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene);
bool isInScene() const { return render::Item::isValidID(_renderItemID); }
// Some rate tracking support
RateCounter<> _simulationRate;
RateCounter<> _simulationInViewRate;
@ -326,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<float>::max() };
float _smoothOrientationTime { SMOOTH_TIME_ORIENTATION };
float _smoothOrientationTimer { std::numeric_limits<float>::max() };
glm::vec3 _smoothPositionInitial;
glm::vec3 _smoothPositionTarget;
glm::quat _smoothOrientationInitial;
@ -350,17 +361,19 @@ private:
int _leftPointerGeometryID { 0 };
int _rightPointerGeometryID { 0 };
int _nameRectGeometryID { 0 };
bool _initialized;
bool _initialized { false };
bool _isLookAtTarget { false };
bool _isAnimatingScale { false };
float getBoundingRadius() const;
static int _jointConesID;
int _voiceSphereID;
AvatarMotionState* _motionState = nullptr;
AvatarPhysicsCallback _physicsCallback { nullptr };
float _displayNameTargetAlpha { 1.0f };
float _displayNameAlpha { 1.0f };
};
#endif // hifi_Avatar_h

View file

@ -9,15 +9,14 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AvatarMotionState.h"
#include <PhysicsCollisionGroups.h>
#include <PhysicsEngine.h>
#include <PhysicsHelpers.h>
#include "Avatar.h"
#include "AvatarMotionState.h"
#include "BulletUtil.h"
AvatarMotionState::AvatarMotionState(Avatar* avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
assert(_avatar);
_type = MOTIONSTATE_TYPE_AVATAR;
if (_shape) {
@ -49,7 +48,7 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const {
// virtual and protected
const btCollisionShape* AvatarMotionState::computeNewShape() {
ShapeInfo shapeInfo;
_avatar->computeShapeInfo(shapeInfo);
std::static_pointer_cast<Avatar>(_avatar)->computeShapeInfo(shapeInfo);
return getShapeManager()->getShape(shapeInfo);
}
@ -130,7 +129,7 @@ glm::vec3 AvatarMotionState::getObjectAngularVelocity() const {
// virtual
glm::vec3 AvatarMotionState::getObjectGravity() const {
return _avatar->getAcceleration();
return std::static_pointer_cast<Avatar>(_avatar)->getAcceleration();
}
// virtual

View file

@ -15,12 +15,13 @@
#include <QSet>
#include <ObjectMotionState.h>
#include <BulletUtil.h>
class Avatar;
#include "Avatar.h"
class AvatarMotionState : public ObjectMotionState {
public:
AvatarMotionState(Avatar* avatar, const btCollisionShape* shape);
AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape);
virtual PhysicsMotionType getMotionType() const override { return _motionType; }
@ -74,11 +75,7 @@ protected:
virtual bool isReadyToComputeShape() const override { return true; }
virtual const btCollisionShape* computeNewShape() override;
// The AvatarMotionState keeps a RAW backpointer to its Avatar because all AvatarMotionState
// instances are "owned" by their corresponding Avatar instance and are deleted in the Avatar dtor.
// In other words, it is impossible for the Avatar to be deleted out from under its MotionState.
// In conclusion: weak pointer shennanigans would be pure overhead.
Avatar* _avatar; // do NOT use smartpointer here, no need for weakpointer
AvatarSharedPointer _avatar;
uint32_t _dirtyFlags;
};

View file

@ -8,56 +8,28 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Head.h"
#include <glm/gtx/quaternion.hpp>
#include <gpu/Batch.h>
#include <NodeList.h>
#include <recording/Deck.h>
#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 "devices/EyeTracker.h"
#include "devices/Faceshift.h"
#include <DependencyManager.h>
#include <GeometryUtil.h>
#include <trackers/FaceTracker.h>
#include <trackers/EyeTracker.h>
#include <Rig.h>
#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<GeometryCache>()->allocateID()),
_rightEyeLookAtID(DependencyManager::get<GeometryCache>()->allocateID())
{
@ -70,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
@ -91,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<recording::Deck>();
// 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<EyeTracker>();
_isEyeTrackerConnected = eyeTracker->isTracking();
}
}
if (!_isFaceTrackerConnected) {
if (!_isEyeTrackerConnected) {
// Update eye saccades
const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f;
@ -209,21 +145,21 @@ void Head::simulate(float deltaTime, bool isMine) {
// use data to update fake Faceshift blendshape coefficients
calculateMouthShapes(deltaTime);
DependencyManager::get<Faceshift>()->updateFakeCoefficients(_leftEyeBlink,
_rightEyeBlink,
_browAudioLift,
_audioJawOpen,
_mouth2,
_mouth3,
_mouth4,
_blendshapeCoefficients);
FaceTracker::updateFakeCoefficients(_leftEyeBlink,
_rightEyeBlink,
_browAudioLift,
_audioJawOpen,
_mouth2,
_mouth3,
_mouth4,
_blendshapeCoefficients);
applyEyelidOffset(getOrientation());
} 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();
}
@ -278,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;
}
@ -351,7 +287,7 @@ glm::vec3 Head::getCorrectedLookAtPosition() {
}
}
void Head::setCorrectedLookAtPosition(glm::vec3 correctedLookAtPosition) {
void Head::setCorrectedLookAtPosition(const glm::vec3& correctedLookAtPosition) {
if (!isLookingAtMe()) {
_lookingAtMeStarted = usecTimestampNow();
}
@ -367,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<MyAvatar*>(_owningAvatar);
if (myAvatar) {
return glm::quat_cast(myAvatar->getSensorToWorldMatrix()) * myAvatar->getHMDSensorOrientation();
} else {
return getOrientation();
}
} else {
Avatar* owningAvatar = static_cast<Avatar*>(_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;

View file

@ -11,16 +11,10 @@
#ifndef hifi_Head_h
#define hifi_Head_h
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
#include <GLMHelpers.h>
#include <SharedUtil.h>
#include <HeadData.h>
#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;

View file

@ -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")

View file

@ -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<SkeletonModel>(this, nullptr, rig);
connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
}

View file

@ -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

View file

@ -9,19 +9,18 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "SkeletonModel.h"
#include <glm/gtx/transform.hpp>
#include <QMultiMap>
#include <recording/Deck.h>
#include <DebugDraw.h>
#include <AnimDebugDraw.h>
#include <CharacterController.h>
#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,29 +71,15 @@ 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) {
assert(!_owningAvatar->isMyAvatar());
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();
glm::vec3 lookAt = head->getCorrectedLookAtPosition();
glm::vec3 focusOffset = lookAt - _owningAvatar->getHead()->getEyePosition();
float focusDistance = glm::length(focusOffset);
const float MIN_LOOK_AT_FOCUS_DISTANCE = 1.0f;
@ -102,139 +87,36 @@ 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<MyAvatar*>(_owningAvatar);
// 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;
Rig::HeadParameters headParams;
// This is a little more work than we really want.
//
// Other avatars joint, including their eyes, should already be set just like any other joints
// from the wire data. But when looking at me, we want the eyes to use the corrected lookAt.
//
// Thus this should really only be ... else if (_owningAvatar->getHead()->isLookingAtMe()) {...
// However, in the !isLookingAtMe case, the eyes aren't rotating the way they should right now.
// We will revisit that as priorities allow, and particularly after the new rig/animation/joints.
if (qApp->isHMDMode()) {
headParams.isInHMD = true;
// If the head is not positioned, updateEyeJoints won't get the math right
glm::quat headOrientation;
_rig->getJointRotation(geometry.headJointIndex, headOrientation);
glm::vec3 eulers = safeEulerAngles(headOrientation);
head->setBasePitch(glm::degrees(-eulers.x));
head->setBaseYaw(glm::degrees(eulers.y));
head->setBaseRoll(glm::degrees(-eulers.z));
// 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::EyeParameters eyeParams;
eyeParams.eyeLookAt = lookAt;
eyeParams.eyeSaccade = glm::vec3(0.0f);
eyeParams.modelRotation = getRotation();
eyeParams.modelTranslation = getTranslation();
eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex;
eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex;
headParams.rigHeadPosition = extractTranslation(rigHMDMat);
headParams.rigHeadOrientation = extractRotation(rigHMDMat);
headParams.worldHeadOrientation = extractRotation(worldHMDMat);
// TODO: if hips target sensor is valid.
// Copy it into headParams.hipsMatrix, and set headParams.hipsEnabled to true.
headParams.hipsEnabled = false;
} else {
headParams.hipsEnabled = false;
headParams.isInHMD = false;
// We don't have a valid localHeadPosition.
headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame();
headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame();
}
headParams.neckJointIndex = geometry.neckJointIndex;
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.worldHeadOrientation = headParams.worldHeadOrientation;
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 {
// 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;
// This is a little more work than we really want.
//
// Other avatars joint, including their eyes, should already be set just like any other joints
// from the wire data. But when looking at me, we want the eyes to use the corrected lookAt.
//
// Thus this should really only be ... else if (_owningAvatar->getHead()->isLookingAtMe()) {...
// However, in the !isLookingAtMe case, the eyes aren't rotating the way they should right now.
// We will revisit that as priorities allow, and particularly after the new rig/animation/joints.
// If the head is not positioned, updateEyeJoints won't get the math right
glm::quat headOrientation;
_rig->getJointRotation(geometry.headJointIndex, headOrientation);
glm::vec3 eulers = safeEulerAngles(headOrientation);
head->setBasePitch(glm::degrees(-eulers.x));
head->setBaseYaw(glm::degrees(eulers.y));
head->setBaseRoll(glm::degrees(-eulers.z));
Rig::EyeParameters eyeParams;
eyeParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame();
eyeParams.eyeLookAt = lookAt;
eyeParams.eyeSaccade = glm::vec3(0.0f);
eyeParams.modelRotation = getRotation();
eyeParams.modelTranslation = getTranslation();
eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex;
eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex;
_rig->updateFromEyeParameters(eyeParams);
}
_rig->updateFromEyeParameters(eyeParams);
}
void SkeletonModel::updateAttitude() {
@ -250,7 +132,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;
@ -258,7 +140,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
setOffset(registrationPoint);
}
} else {
Model::simulate(deltaTime, fullUpdate);
Parent::simulate(deltaTime, fullUpdate);
}
if (!isActive() || !_owningAvatar->isMyAvatar()) {

View file

@ -23,6 +23,7 @@ using SkeletonModelWeakPointer = std::weak_ptr<SkeletonModel>;
/// 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;

View file

@ -63,8 +63,6 @@ AvatarData::AvatarData() :
_keyState(NO_KEY_DOWN),
_forceFaceTrackerConnected(false),
_headData(NULL),
_displayNameTargetAlpha(1.0f),
_displayNameAlpha(1.0f),
_errorLogExpiry(0),
_owningAvatarMixer(),
_targetVelocity(0.0f)
@ -393,9 +391,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
if (isFingerPointing) {
setAtBit(flags, HAND_STATE_FINGER_POINTING_BIT);
}
// faceshift state
// face tracker state
if (_headData->_isFaceTrackerConnected) {
setAtBit(flags, IS_FACESHIFT_CONNECTED);
setAtBit(flags, IS_FACE_TRACKER_CONNECTED);
}
// eye tracker state
if (_headData->_isEyeTrackerConnected) {
@ -883,7 +881,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
auto newHandState = getSemiNibbleAt(bitItems, HAND_STATE_START_BIT)
+ (oneAtBit(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0);
auto newFaceTrackerConnected = oneAtBit(bitItems, IS_FACESHIFT_CONNECTED);
auto newFaceTrackerConnected = oneAtBit(bitItems, IS_FACE_TRACKER_CONNECTED);
auto newEyeTrackerConnected = oneAtBit(bitItems, IS_EYE_TRACKER_CONNECTED);
bool keyStateChanged = (_keyState != newKeyState);

View file

@ -94,12 +94,12 @@ 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
const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits
const int IS_FACESHIFT_CONNECTED = 4; // 5th bit
const int IS_FACE_TRACKER_CONNECTED = 4; // 5th bit
const int IS_EYE_TRACKER_CONNECTED = 5; // 6th bit (was CHAT_CIRCLING)
const int HAS_REFERENTIAL = 6; // 7th bit
const int HAND_STATE_FINGER_POINTING_BIT = 7; // 8th bit
@ -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;
@ -218,7 +218,7 @@ namespace AvatarDataPacket {
} PACKED_END;
const size_t AVATAR_LOCAL_POSITION_SIZE = 12;
// only present if IS_FACESHIFT_CONNECTED flag is set in AvatarInfo.flags
// only present if IS_FACE_TRACKER_CONNECTED flag is set in AvatarInfo.flags
PACKED_BEGIN struct FaceTrackerInfo {
float leftEyeBlink;
float rightEyeBlink;
@ -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);
@ -692,9 +694,6 @@ protected:
QString _sessionDisplayName { };
QUrl cannonicalSkeletonModelURL(const QUrl& empty) const;
float _displayNameTargetAlpha;
float _displayNameAlpha;
QHash<QString, int> _jointIndices; ///< 1-based, since zero is returned for missing keys
QStringList _jointNames; ///< in order of depth-first traversal

View file

@ -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),

View file

@ -53,6 +53,9 @@ namespace controller {
makePosePair(Action::RIGHT_HAND, "RightHand"),
makePosePair(Action::LEFT_FOOT, "LeftFoot"),
makePosePair(Action::RIGHT_FOOT, "RightFoot"),
makePosePair(Action::HIPS, "Hips"),
makePosePair(Action::SPINE2, "Spine2"),
makePosePair(Action::HEAD, "Head"),
makeButtonPair(Action::LEFT_HAND_CLICK, "LeftHandClick"),
makeButtonPair(Action::RIGHT_HAND_CLICK, "RightHandClick"),

View file

@ -44,6 +44,9 @@ enum class Action {
RIGHT_HAND,
LEFT_FOOT,
RIGHT_FOOT,
HIPS,
SPINE2,
HEAD,
LEFT_HAND_CLICK,
RIGHT_HAND_CLICK,

View file

@ -16,9 +16,15 @@
namespace controller {
struct InputCalibrationData {
glm::mat4 sensorToWorldMat;
glm::mat4 avatarMat;
glm::mat4 hmdSensorMat;
glm::mat4 sensorToWorldMat; // sensor to world
glm::mat4 avatarMat; // avatar to world
glm::mat4 hmdSensorMat; // hmd pos and orientation in sensor space
glm::mat4 defaultCenterEyeMat; // default pose for the center of the eyes in avatar space.
glm::mat4 defaultHeadMat; // default pose for head joint in avatar space
glm::mat4 defaultSpine2; // default pose for spine2 joint in avatar space
glm::mat4 defaultHips; // default pose for hips joint in avatar space
glm::mat4 defaultLeftFoot; // default pose for leftFoot joint in avatar space
glm::mat4 defaultRightFoot; // default pose for leftFoot joint in avatar space
};
enum class ChannelType {

View file

@ -69,5 +69,23 @@ namespace controller {
pose.valid = valid;
return pose;
}
Pose Pose::postTransform(const glm::mat4& mat) const {
glm::mat4 original = ::createMatFromQuatAndPos(rotation, translation);
glm::mat4 result = original * mat;
auto translationOut = ::extractTranslation(result);
auto rotationOut = ::glmExtractRotation(result);
auto velocityOut = velocity + glm::cross(angularVelocity, translationOut - translation); // warning: this may be completely wrong
auto angularVelocityOut = angularVelocity;
Pose pose(translationOut,
rotationOut,
velocityOut,
angularVelocityOut);
pose.valid = valid;
return pose;
}
}

View file

@ -41,6 +41,7 @@ namespace controller {
vec3 getAngularVelocity() const { return angularVelocity; }
Pose transform(const glm::mat4& mat) const;
Pose postTransform(const glm::mat4& mat) const;
static QScriptValue toScriptValue(QScriptEngine* engine, const Pose& event);
static void fromScriptValue(const QScriptValue& object, Pose& event);

View file

@ -104,6 +104,9 @@ Input::NamedVector StandardController::getAvailableInputs() const {
makePair(RIGHT_HAND, "RightHand"),
makePair(LEFT_FOOT, "LeftFoot"),
makePair(RIGHT_FOOT, "RightFoot"),
makePair(HIPS, "Hips"),
makePair(SPINE2, "Spine2"),
makePair(HEAD, "Head"),
// Aliases, PlayStation style names
makePair(LB, "L1"),

View file

@ -20,6 +20,8 @@
#include <PathUtils.h>
#include <NumericalConstants.h>
#include <StreamUtils.h>
#include "StandardController.h"
#include "StateController.h"
#include "InputRecorder.h"
@ -563,7 +565,18 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) {
if (source->isPose()) {
Pose value = getPose(source, route->peek);
static const Pose IDENTITY_POSE { vec3(), quat() };
if (debugRoutes && route->debug) {
qCDebug(controllers) << "Value was t:" << value.translation << "r:" << value.rotation;
}
// Apply each of the filters.
for (const auto& filter : route->filters) {
value = filter->apply(value);
}
if (debugRoutes && route->debug) {
qCDebug(controllers) << "Filtered value was t:" << value.translation << "r:" << value.rotation;
if (!value.valid) {
qCDebug(controllers) << "Applying invalid pose";
} else if (value == IDENTITY_POSE) {

View file

@ -24,6 +24,10 @@
#include "filters/InvertFilter.h"
#include "filters/PulseFilter.h"
#include "filters/ScaleFilter.h"
#include "filters/TranslateFilter.h"
#include "filters/TransformFilter.h"
#include "filters/PostTransformFilter.h"
#include "filters/RotateFilter.h"
using namespace controller;
@ -37,6 +41,10 @@ REGISTER_FILTER_CLASS_INSTANCE(HysteresisFilter, "hysteresis")
REGISTER_FILTER_CLASS_INSTANCE(InvertFilter, "invert")
REGISTER_FILTER_CLASS_INSTANCE(ScaleFilter, "scale")
REGISTER_FILTER_CLASS_INSTANCE(PulseFilter, "pulse")
REGISTER_FILTER_CLASS_INSTANCE(TranslateFilter, "translate")
REGISTER_FILTER_CLASS_INSTANCE(TransformFilter, "transform")
REGISTER_FILTER_CLASS_INSTANCE(PostTransformFilter, "postTransform")
REGISTER_FILTER_CLASS_INSTANCE(RotateFilter, "rotate")
const QString JSON_FILTER_TYPE = QStringLiteral("type");
const QString JSON_FILTER_PARAMS = QStringLiteral("params");
@ -76,7 +84,6 @@ bool Filter::parseSingleFloatParameter(const QJsonValue& parameters, const QStri
return true;
}
} else if (parameters.isObject()) {
static const QString JSON_MIN = QStringLiteral("interval");
auto objectParameters = parameters.toObject();
if (objectParameters.contains(name)) {
output = objectParameters[name].toDouble();
@ -86,6 +93,92 @@ bool Filter::parseSingleFloatParameter(const QJsonValue& parameters, const QStri
return false;
}
bool Filter::parseVec3Parameter(const QJsonValue& parameters, glm::vec3& output) {
if (parameters.isDouble()) {
output = glm::vec3(parameters.toDouble());
return true;
} else if (parameters.isArray()) {
auto arrayParameters = parameters.toArray();
if (arrayParameters.size() == 3) {
output = glm::vec3(arrayParameters[0].toDouble(),
arrayParameters[1].toDouble(),
arrayParameters[2].toDouble());
return true;
}
} else if (parameters.isObject()) {
auto objectParameters = parameters.toObject();
if (objectParameters.contains("x") && objectParameters.contains("y") && objectParameters.contains("z")) {
output = glm::vec3(objectParameters["x"].toDouble(),
objectParameters["y"].toDouble(),
objectParameters["z"].toDouble());
return true;
}
}
return false;
}
bool Filter::parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output) {
if (parameters.isObject()) {
auto objectParameters = parameters.toObject();
if (objectParameters.contains("r0c0") &&
objectParameters.contains("r1c0") &&
objectParameters.contains("r2c0") &&
objectParameters.contains("r3c0") &&
objectParameters.contains("r0c1") &&
objectParameters.contains("r1c1") &&
objectParameters.contains("r2c1") &&
objectParameters.contains("r3c1") &&
objectParameters.contains("r0c2") &&
objectParameters.contains("r1c2") &&
objectParameters.contains("r2c2") &&
objectParameters.contains("r3c2") &&
objectParameters.contains("r0c3") &&
objectParameters.contains("r1c3") &&
objectParameters.contains("r2c3") &&
objectParameters.contains("r3c3")) {
output[0][0] = objectParameters["r0c0"].toDouble();
output[0][1] = objectParameters["r1c0"].toDouble();
output[0][2] = objectParameters["r2c0"].toDouble();
output[0][3] = objectParameters["r3c0"].toDouble();
output[1][0] = objectParameters["r0c1"].toDouble();
output[1][1] = objectParameters["r1c1"].toDouble();
output[1][2] = objectParameters["r2c1"].toDouble();
output[1][3] = objectParameters["r3c1"].toDouble();
output[2][0] = objectParameters["r0c2"].toDouble();
output[2][1] = objectParameters["r1c2"].toDouble();
output[2][2] = objectParameters["r2c2"].toDouble();
output[2][3] = objectParameters["r3c2"].toDouble();
output[3][0] = objectParameters["r0c3"].toDouble();
output[3][1] = objectParameters["r1c3"].toDouble();
output[3][2] = objectParameters["r2c3"].toDouble();
output[3][3] = objectParameters["r3c3"].toDouble();
return true;
}
}
return false;
}
bool Filter::parseQuatParameter(const QJsonValue& parameters, glm::quat& output) {
if (parameters.isObject()) {
auto objectParameters = parameters.toObject();
if (objectParameters.contains("w") &&
objectParameters.contains("x") &&
objectParameters.contains("y") &&
objectParameters.contains("z")) {
output = glm::quat(objectParameters["w"].toDouble(),
objectParameters["x"].toDouble(),
objectParameters["y"].toDouble(),
objectParameters["z"].toDouble());
return true;
}
}
return false;
}
#if 0

View file

@ -21,6 +21,8 @@
#include <QtCore/QEasingCurve>
#include "../Pose.h"
class QJsonValue;
namespace controller {
@ -34,6 +36,8 @@ namespace controller {
using Factory = hifi::SimpleFactory<Filter, QString>;
virtual float apply(float value) const = 0;
virtual Pose apply(Pose value) const = 0;
// Factory features
virtual bool parseParameters(const QJsonValue& parameters) { return true; }
@ -42,6 +46,9 @@ namespace controller {
static Factory& getFactory() { return _factory; }
static bool parseSingleFloatParameter(const QJsonValue& parameters, const QString& name, float& output);
static bool parseVec3Parameter(const QJsonValue& parameters, glm::vec3& output);
static bool parseQuatParameter(const QJsonValue& parameters, glm::quat& output);
static bool parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output);
protected:
static Factory _factory;
};

View file

@ -26,6 +26,10 @@
#include "filters/InvertFilter.h"
#include "filters/PulseFilter.h"
#include "filters/ScaleFilter.h"
#include "filters/TranslateFilter.h"
#include "filters/TransformFilter.h"
#include "filters/PostTransformFilter.h"
#include "filters/RotateFilter.h"
#include "conditionals/AndConditional.h"
using namespace controller;
@ -103,6 +107,26 @@ QObject* RouteBuilderProxy::deadZone(float min) {
return this;
}
QObject* RouteBuilderProxy::translate(glm::vec3 translate) {
addFilter(std::make_shared<TranslateFilter>(translate));
return this;
}
QObject* RouteBuilderProxy::transform(glm::mat4 transform) {
addFilter(std::make_shared<TransformFilter>(transform));
return this;
}
QObject* RouteBuilderProxy::postTransform(glm::mat4 transform) {
addFilter(std::make_shared<PostTransformFilter>(transform));
return this;
}
QObject* RouteBuilderProxy::rotate(glm::quat rotation) {
addFilter(std::make_shared<RotateFilter>(rotation));
return this;
}
QObject* RouteBuilderProxy::constrainToInteger() {
addFilter(std::make_shared<ConstrainToIntegerFilter>());
return this;

Some files were not shown because too many files have changed in this diff Show more