3
0
Fork 0
mirror of https://github.com/lubosz/overte.git synced 2025-04-26 12:55:27 +02:00

Merge branch 'master' of github.com:highfidelity/hifi into MS15673_bubbleResizeExperiment

This commit is contained in:
Zach Fox 2018-06-11 13:20:34 -07:00
commit dbd10cc2f0
114 changed files with 2023 additions and 1433 deletions
android
app
build.gradle
src/main/java/io/highfidelity/hifiinterface
build.gradle
cmake
domain-server/src
interface
libraries
plugins
scripts
defaultScripts.js
system
+android
commerce
controllers/controllerModules

View file

@ -22,7 +22,7 @@ android {
'-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED,
'-DRELEASE_NUMBER=' + RELEASE_NUMBER,
'-DRELEASE_TYPE=' + RELEASE_TYPE,
'-DBUILD_BRANCH=' + BUILD_BRANCH,
'-DSTABLE_BUILD=' + STABLE_BUILD,
'-DDISABLE_QML=OFF',
'-DDISABLE_KTX_CACHE=OFF'
}
@ -128,4 +128,3 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
}

View file

@ -64,7 +64,11 @@ public class HomeFragment extends Fragment {
mDomainsView.setLayoutManager(gridLayoutMgr);
mDomainAdapter = new DomainAdapter(getContext(), HifiUtils.getInstance().protocolVersionSignature(), nativeGetLastLocation());
mDomainAdapter.setClickListener((view, position, domain) -> {
new Handler(getActivity().getMainLooper()).postDelayed(() -> mListener.onSelectedDomain(domain.url), 400); // a delay so the ripple effect can be seen
new Handler(getActivity().getMainLooper()).postDelayed(() -> {
if (mListener != null) {
mListener.onSelectedDomain(domain.url);
}
}, 400); // a delay so the ripple effect can be seen
});
mDomainAdapter.setListener(new DomainAdapter.AdapterListener() {
@Override
@ -116,7 +120,9 @@ public class HomeFragment extends Fragment {
if (!urlString.trim().isEmpty()) {
urlString = HifiUtils.getInstance().sanitizeHifiUrl(urlString);
}
mListener.onSelectedDomain(urlString);
if (mListener != null) {
mListener.onSelectedDomain(urlString);
}
return true;
}
return false;

View file

@ -1,10 +1,11 @@
package io.highfidelity.hifiinterface.provider;
import android.util.Log;
import android.util.MutableInt;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import io.highfidelity.hifiinterface.HifiUtils;
import io.highfidelity.hifiinterface.view.DomainAdapter;
@ -47,24 +48,42 @@ public class UserStoryDomainProvider implements DomainProvider {
suggestions = new ArrayList<>();
}
@Override
public synchronized void retrieve(String filterText, DomainCallback domainCallback) {
if (!startedToGetFromAPI) {
startedToGetFromAPI = true;
fillDestinations(filterText, domainCallback);
} else {
filterChoicesByText(filterText, domainCallback);
}
}
private void fillDestinations(String filterText, DomainCallback domainCallback) {
StoriesFilter filter = new StoriesFilter(filterText);
final MutableInt counter = new MutableInt(0);
allStories.clear();
getUserStoryPage(1,
List<UserStory> taggedStories = new ArrayList<>();
Set<String> taggedStoriesIds = new HashSet<>();
getUserStoryPage(1, taggedStories, TAGS_TO_SEARCH,
e -> {
allStories.subList(counter.value, allStories.size()).forEach(userStory -> {
filter.filterOrAdd(userStory);
});
if (domainCallback != null) {
domainCallback.retrieveOk(suggestions); //ended
}
},
a -> {
allStories.forEach(userStory -> {
counter.value++;
filter.filterOrAdd(userStory);
taggedStories.forEach(userStory -> {
taggedStoriesIds.add(userStory.id);
});
allStories.clear();
getUserStoryPage(1, allStories, null,
ex -> {
allStories.forEach(userStory -> {
if (taggedStoriesIds.contains(userStory.id)) {
userStory.tagFound = true;
}
filter.filterOrAdd(userStory);
});
if (domainCallback != null) {
domainCallback.retrieveOk(suggestions); //ended
}
}
);
}
);
}
@ -73,25 +92,22 @@ public class UserStoryDomainProvider implements DomainProvider {
restOfPagesCallback.callback(new Exception("Error accessing url [" + url + "]", t));
}
private void getUserStoryPage(int pageNumber, Callback<Exception> restOfPagesCallback, Callback<Void> firstPageCallback) {
private void getUserStoryPage(int pageNumber, List<UserStory> userStoriesList, String tagsFilter, Callback<Exception> restOfPagesCallback) {
Call<UserStories> userStories = mUserStoryDomainProviderService.getUserStories(
INCLUDE_ACTIONS_FOR_PLACES,
"open",
true,
mProtocol,
TAGS_TO_SEARCH,
tagsFilter,
pageNumber);
Log.d("API-USER-STORY-DOMAINS", "Protocol [" + mProtocol + "] include_actions [" + INCLUDE_ACTIONS_FOR_PLACES + "]");
userStories.enqueue(new retrofit2.Callback<UserStories>() {
@Override
public void onResponse(Call<UserStories> call, Response<UserStories> response) {
UserStories data = response.body();
allStories.addAll(data.user_stories);
userStoriesList.addAll(data.user_stories);
if (data.current_page < data.total_pages && data.current_page <= MAX_PAGES_TO_GET) {
if (pageNumber == 1 && firstPageCallback != null) {
firstPageCallback.callback(null);
}
getUserStoryPage(pageNumber + 1, restOfPagesCallback, null);
getUserStoryPage(pageNumber + 1, userStoriesList, tagsFilter, restOfPagesCallback);
return;
}
restOfPagesCallback.callback(null);
@ -107,12 +123,16 @@ public class UserStoryDomainProvider implements DomainProvider {
private class StoriesFilter {
String[] mWords = new String[]{};
public StoriesFilter(String filterText) {
mWords = filterText.toUpperCase().split("\\s+");
mWords = filterText.trim().toUpperCase().split("\\s+");
if (mWords.length == 1 && (mWords[0] == null || mWords[0].length() <= 0 ) ) {
mWords = null;
}
}
private boolean matches(UserStory story) {
if (mWords.length <= 0) {
return true;
if (mWords == null || mWords.length <= 0) {
// No text filter? So filter by tag
return story.tagFound;
}
for (String word : mWords) {
@ -128,6 +148,9 @@ public class UserStoryDomainProvider implements DomainProvider {
suggestions.add(story.toDomain());
}
/**
* if the story matches this filter criteria it's added into suggestions
* */
public void filterOrAdd(UserStory story) {
if (matches(story)) {
addToSuggestions(story);
@ -144,16 +167,6 @@ public class UserStoryDomainProvider implements DomainProvider {
domainCallback.retrieveOk(suggestions);
}
@Override
public synchronized void retrieve(String filterText, DomainCallback domainCallback) {
if (!startedToGetFromAPI) {
startedToGetFromAPI = true;
fillDestinations(filterText, domainCallback);
} else {
filterChoicesByText(filterText, domainCallback);
}
}
public interface UserStoryDomainProviderService {
@GET("api/v1/user_stories")
Call<UserStories> getUserStories(@Query("include_actions") String includeActions,
@ -166,12 +179,14 @@ public class UserStoryDomainProvider implements DomainProvider {
class UserStory {
public UserStory() {}
String id;
String place_name;
String path;
String thumbnail_url;
String place_id;
String domain_id;
private String searchText;
private boolean tagFound; // Locally used
// New fields? tags, description

View file

@ -54,27 +54,10 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
@Override
public void retrieveOk(List<Domain> domain) {
if (filterText.length() == 0) {
Domain lastVisitedDomain = new Domain(mContext.getString(R.string.your_last_location), mLastLocation, DEFAULT_THUMBNAIL_PLACE);
if (!mLastLocation.isEmpty() && mLastLocation.contains("://")) {
int startIndex = mLastLocation.indexOf("://");
int endIndex = mLastLocation.indexOf("/", startIndex + 3);
String toSearch = mLastLocation.substring(0, endIndex + 1).toLowerCase();
for (Domain d : domain) {
if (d.url.toLowerCase().startsWith(toSearch)) {
lastVisitedDomain.thumbnail = d.thumbnail;
}
}
}
domain.add(0, lastVisitedDomain);
addLastLocation(domain);
}
for (Domain d : domain) {
// we override the default picture added in the server by an android specific version
if (d.thumbnail != null &&
d.thumbnail.endsWith("assets/places/thumbnail-default-place-e5a3f33e773ab699495774990a562f9f7693dc48ef90d8be6985c645a0280f75.png")) {
d.thumbnail = DEFAULT_THUMBNAIL_PLACE;
}
}
overrideDefaultThumbnails(domain);
mDomains = new Domain[domain.size()];
mDomains = domain.toArray(mDomains);
@ -96,6 +79,31 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
});
}
private void overrideDefaultThumbnails(List<Domain> domain) {
for (Domain d : domain) {
// we override the default picture added in the server by an android specific version
if (d.thumbnail != null &&
d.thumbnail.endsWith("assets/places/thumbnail-default-place-e5a3f33e773ab699495774990a562f9f7693dc48ef90d8be6985c645a0280f75.png")) {
d.thumbnail = DEFAULT_THUMBNAIL_PLACE;
}
}
}
private void addLastLocation(List<Domain> domain) {
Domain lastVisitedDomain = new Domain(mContext.getString(R.string.your_last_location), mLastLocation, DEFAULT_THUMBNAIL_PLACE);
if (!mLastLocation.isEmpty() && mLastLocation.contains("://")) {
int startIndex = mLastLocation.indexOf("://");
int endIndex = mLastLocation.indexOf("/", startIndex + 3);
String toSearch = mLastLocation.substring(0, endIndex + 1).toLowerCase();
for (Domain d : domain) {
if (d.url.toLowerCase().startsWith(toSearch)) {
lastVisitedDomain.thumbnail = d.thumbnail;
}
}
}
domain.add(0, lastVisitedDomain);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.domain_view, parent, false);

View file

@ -37,7 +37,7 @@ task clean(type: Delete) {
ext {
RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0'
RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV'
BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : ''
STABLE_BUILD = project.hasProperty('STABLE_BUILD') ? project.getProperty('STABLE_BUILD') : '0'
EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : ''
QT5_DEPS = [
'Qt5Concurrent',
@ -542,7 +542,7 @@ task cleanDependencies(type: Delete) {
// FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution.
// FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution.
// See the comment on the qtBundle task above
/*
// FIXME derive the path from the gradle environment

View file

@ -17,8 +17,8 @@ if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://static.oculus.com/sdk-downloads/1.11.0/Public/1486063832/ovr_sdk_win_1.11.0_public.zip
URL_MD5 ea484403757cbfdfa743b6577fb1f9d2
URL http://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.26.0_public.zip
URL_MD5 06804ff9727b910dcd04a37c800053b5
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/LibOVRCMakeLists.txt" <SOURCE_DIR>/CMakeLists.txt
LOG_DOWNLOAD 1

View file

@ -4,7 +4,7 @@ project(LibOVR)
include_directories(LibOVR/Include LibOVR/Src)
file(GLOB HEADER_FILES LibOVR/Include/*.h)
file(GLOB EXTRA_HEADER_FILES LibOVR/Include/Extras/*.h)
file(GLOB_RECURSE SOURCE_FILES LibOVR/Src/*.c LibOVR/Src/*.cpp)
file(GLOB_RECURSE SOURCE_FILES LibOVR/Shim/*.c LibOVR/Shim/*.cpp)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DOVR_BUILD_DEBUG")
add_library(LibOVR STATIC ${SOURCE_FILES} ${HEADER_FILES} ${EXTRA_HEADER_FILES})
set_target_properties(LibOVR PROPERTIES DEBUG_POSTFIX "d")

View file

@ -17,14 +17,12 @@ macro(SET_PACKAGING_PARAMETERS)
set(DEV_BUILD 0)
set(BUILD_GLOBAL_SERVICES "DEVELOPMENT")
set(USE_STABLE_GLOBAL_SERVICES 0)
set(BUILD_NUMBER 0)
set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV")
set_from_env(RELEASE_NUMBER RELEASE_NUMBER "")
set_from_env(BUILD_BRANCH BRANCH "")
string(TOLOWER "${BUILD_BRANCH}" BUILD_BRANCH)
set_from_env(STABLE_BUILD STABLE_BUILD 0)
message(STATUS "The BUILD_BRANCH variable is: ${BUILD_BRANCH}")
message(STATUS "The BRANCH environment variable is: $ENV{BRANCH}")
message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}")
# setup component categories for installer
@ -46,17 +44,17 @@ macro(SET_PACKAGING_PARAMETERS)
# if the build is a PRODUCTION_BUILD from the "stable" branch
# then use the STABLE gobal services
if (BUILD_BRANCH STREQUAL "stable")
message(STATUS "The RELEASE_TYPE is PRODUCTION and the BUILD_BRANCH is stable...")
if (STABLE_BUILD)
message(STATUS "The RELEASE_TYPE is PRODUCTION and STABLE_BUILD is 1")
set(BUILD_GLOBAL_SERVICES "STABLE")
set(USE_STABLE_GLOBAL_SERVICES 1)
endif()
endif ()
elseif (RELEASE_TYPE STREQUAL "PR")
set(DEPLOY_PACKAGE TRUE)
set(PR_BUILD 1)
set(BUILD_VERSION "PR${RELEASE_NUMBER}")
set(BUILD_ORGANIZATION "High Fidelity - ${BUILD_VERSION}")
set(BUILD_ORGANIZATION "High Fidelity - PR${RELEASE_NUMBER}")
set(INTERFACE_BUNDLE_NAME "Interface")
set(INTERFACE_ICON_PREFIX "interface-beta")
@ -75,6 +73,54 @@ macro(SET_PACKAGING_PARAMETERS)
string(TIMESTAMP BUILD_TIME "%d/%m/%Y")
# if STABLE_BUILD is 1, PRODUCTION_BUILD must be 1 and
# DEV_BUILD and PR_BUILD must be 0
if (STABLE_BUILD)
if ((NOT PRODUCTION_BUILD) OR PR_BUILD OR DEV_BUILD)
message(FATAL_ERROR "Cannot produce STABLE_BUILD without PRODUCTION_BUILD")
endif ()
endif ()
if ((PRODUCTION_BUILD OR PR_BUILD) AND NOT STABLE_BUILD)
# append the abbreviated commit SHA to the build version
# since this is a PR build or master/nightly builds
# for PR_BUILDS, we need to grab the abbreviated SHA
# for the second parent of HEAD (not HEAD) since that is the
# SHA of the commit merged to master for the build
if (PR_BUILD)
set(_GIT_LOG_FORMAT "%p")
else ()
set(_GIT_LOG_FORMAT "%h")
endif ()
execute_process(
COMMAND git log -1 --format=${_GIT_LOG_FORMAT}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE _GIT_LOG_OUTPUT
ERROR_VARIABLE _GIT_LOG_ERROR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if (PR_BUILD)
separate_arguments(_COMMIT_PARENTS UNIX_COMMAND ${_GIT_LOG_OUTPUT})
list(GET _COMMIT_PARENTS 1 GIT_COMMIT_HASH)
else ()
set(GIT_COMMIT_HASH ${_GIT_LOG_OUTPUT})
endif ()
if (_GIT_LOG_ERROR OR NOT GIT_COMMIT_HASH)
message(FATAL_ERROR "Could not retreive abbreviated SHA for PR or production master build")
endif ()
set(BUILD_VERSION_NO_SHA ${BUILD_VERSION})
set(BUILD_VERSION "${BUILD_VERSION}-${GIT_COMMIT_HASH}")
# pass along a release number without the SHA in case somebody
# wants to compare master or PR builds as integers
set(BUILD_NUMBER ${RELEASE_NUMBER})
endif ()
if (DEPLOY_PACKAGE)
# for deployed packages always grab the serverless content
set(DOWNLOAD_SERVERLESS_CONTENT ON)
@ -127,8 +173,8 @@ macro(SET_PACKAGING_PARAMETERS)
set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface")
set(CONSOLE_SHORTCUT_NAME "Sandbox")
else ()
set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface - ${BUILD_VERSION}")
set(CONSOLE_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION}")
set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface - ${BUILD_VERSION_NO_SHA}")
set(CONSOLE_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION_NO_SHA}")
endif ()
set(INTERFACE_HF_SHORTCUT_NAME "${INTERFACE_SHORTCUT_NAME}")

View file

@ -24,8 +24,26 @@ namespace BuildInfo {
const QString MODIFIED_ORGANIZATION = "@BUILD_ORGANIZATION@";
const QString ORGANIZATION_DOMAIN = "highfidelity.io";
const QString VERSION = "@BUILD_VERSION@";
const QString BUILD_BRANCH = "@BUILD_BRANCH@";
const QString BUILD_NUMBER = "@BUILD_NUMBER@";
const QString BUILD_GLOBAL_SERVICES = "@BUILD_GLOBAL_SERVICES@";
const QString BUILD_TIME = "@BUILD_TIME@";
}
enum BuildType {
Dev,
PR,
Master,
Stable
};
#if defined(PR_BUILD)
const BuildType BUILD_TYPE = PR;
const QString BUILD_TYPE_STRING = "pr";
#elif defined(PRODUCTION_BUILD)
const BuildType BUILD_TYPE = @STABLE_BUILD@ ? Stable : Master;
const QString BUILD_TYPE_STRING = @STABLE_BUILD@ ? "stable" : "master";
#else
const BuildType BUILD_TYPE = Dev;
const QString BUILD_TYPE_STRING = "dev";
#endif
}

View file

@ -11,6 +11,35 @@
include(BundleUtilities)
# replace copy_resolved_item_into_bundle
#
# The official version of copy_resolved_item_into_bundle will print out a "warning:" when
# the resolved item matches the resolved embedded item. This not not really an issue that
# should rise to the level of a "warning" so we replace this message with a "status:"
#
# Source: https://github.com/jherico/OculusMinimalExample/blob/master/cmake/templates/FixupBundlePostBuild.cmake.in
#
function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item)
if (WIN32)
# ignore case on Windows
string(TOLOWER "${resolved_item}" resolved_item_compare)
string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare)
else()
set(resolved_item_compare "${resolved_item}")
set(resolved_embedded_item_compare "${resolved_embedded_item}")
endif()
if ("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}")
# this is our only change from the original version
message(STATUS "status: resolved_item == resolved_embedded_item - not copying...")
else()
execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}")
if (UNIX AND NOT APPLE)
file(RPATH_REMOVE FILE "${resolved_embedded_item}")
endif()
endif()
endfunction()
function(gp_resolved_file_type_override resolved_file type_var)
if( file MATCHES ".*VCRUNTIME140.*" )
set(type "system" PARENT_SCOPE)

View file

@ -1,4 +1,5 @@
{
"releaseType": "@RELEASE_TYPE@",
"buildIdentifier": "@BUILD_VERSION@"
"buildIdentifier": "@BUILD_VERSION@",
"organization": "@BUILD_ORGANIZATION@"
}

View file

@ -34,8 +34,9 @@ static const chrono::minutes MAX_REFRESH_TIME { 5 };
Q_DECLARE_LOGGING_CATEGORY(asset_backup)
Q_LOGGING_CATEGORY(asset_backup, "hifi.asset-backup");
AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory) :
_assetsDirectory(backupDirectory + ASSETS_DIR)
AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory, bool assetServerEnabled) :
_assetsDirectory(backupDirectory + ASSETS_DIR),
_assetServerEnabled(assetServerEnabled)
{
// Make sure the asset directory exists.
QDir(_assetsDirectory).mkpath(".");
@ -53,6 +54,7 @@ void AssetsBackupHandler::setupRefreshTimer() {
auto nodeList = DependencyManager::get<LimitedNodeList>();
QObject::connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, [this](SharedNodePointer node) {
if (node->getType() == NodeType::AssetServer) {
assert(_assetServerEnabled);
// run immediately for the first time.
_mappingsRefreshTimer.start(0);
}
@ -233,12 +235,12 @@ void AssetsBackupHandler::createBackup(const QString& backupName, QuaZip& zip) {
return;
}
if (_lastMappingsRefresh.time_since_epoch().count() == 0) {
if (_assetServerEnabled && _lastMappingsRefresh.time_since_epoch().count() == 0) {
qCWarning(asset_backup) << "Current mappings not yet loaded.";
return;
}
if ((p_high_resolution_clock::now() - _lastMappingsRefresh) > MAX_REFRESH_TIME) {
if (_assetServerEnabled && (p_high_resolution_clock::now() - _lastMappingsRefresh) > MAX_REFRESH_TIME) {
qCWarning(asset_backup) << "Backing up asset mappings that might be stale.";
}

View file

@ -30,7 +30,7 @@ class AssetsBackupHandler : public QObject, public BackupHandlerInterface {
Q_OBJECT
public:
AssetsBackupHandler(const QString& backupDirectory);
AssetsBackupHandler(const QString& backupDirectory, bool assetServerEnabled);
std::pair<bool, float> isAvailable(const QString& backupName) override;
std::pair<bool, float> getRecoveryStatus() override;
@ -65,6 +65,7 @@ private:
void updateMappings();
QString _assetsDirectory;
bool _assetServerEnabled { false };
QTimer _mappingsRefreshTimer;
p_high_resolution_clock::time_point _lastMappingsRefresh;

View file

@ -176,7 +176,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
qDebug() << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION;
qDebug() << "[VERSION] VERSION:" << BuildInfo::VERSION;
qDebug() << "[VERSION] BUILD_BRANCH:" << BuildInfo::BUILD_BRANCH;
qDebug() << "[VERSION] BUILD_TYPE_STRING:" << BuildInfo::BUILD_TYPE_STRING;
qDebug() << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES;
qDebug() << "[VERSION] We will be using this name to find ICE servers:" << _iceServerAddr;
@ -307,7 +307,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){
_contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath())));
_contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir())));
_contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir(), isAssetServerEnabled())));
_contentManager->addBackupHandler(BackupHandlerPointer(new ContentSettingsBackupHandler(_settingsManager)));
});
@ -991,15 +991,11 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
defaultedType = static_cast<Assignment::Type>(static_cast<int>(defaultedType) + 1)) {
if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) {
if (defaultedType == Assignment::AssetServerType) {
// Make sure the asset-server is enabled before adding it here.
// Initially we do not assign it by default so we can test it in HF domains first
static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled";
if (!_settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool()) {
// skip to the next iteration if asset-server isn't enabled
continue;
}
// Make sure the asset-server is enabled before adding it here.
// Initially we do not assign it by default so we can test it in HF domains first
if (defaultedType == Assignment::AssetServerType && !isAssetServerEnabled()) {
// skip to the next iteraion if asset-server isn't enabled
continue;
}
// type has not been set from a command line or config file config, use the default
@ -1122,7 +1118,7 @@ void DomainServer::handleConnectedNode(SharedNodePointer newNode) {
}
void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr) {
const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NLPacket::NUM_BYTES_LOCALID +
const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NLPacket::NUM_BYTES_LOCALID +
NUM_BYTES_RFC4122_UUID + NLPacket::NUM_BYTES_LOCALID + 4;
// setup the extended header for the domain list packets
@ -2684,7 +2680,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
QString settingsPassword = settingsPasswordVariant.isValid() ? settingsPasswordVariant.toString() : "";
QString hexHeaderPassword = headerPassword.isEmpty() ?
"" : QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
if (settingsUsername == headerUsername && hexHeaderPassword == settingsPassword) {
return true;
}
@ -2946,6 +2942,12 @@ bool DomainServer::shouldReplicateNode(const Node& node) {
}
};
bool DomainServer::isAssetServerEnabled() {
static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled";
return _settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool();
}
void DomainServer::nodeAdded(SharedNodePointer node) {
// we don't use updateNodeWithData, so add the DomainServerNodeData to the node here
node->setLinkedData(std::unique_ptr<DomainServerNodeData> { new DomainServerNodeData() });

View file

@ -72,6 +72,8 @@ public:
static const QString REPLACEMENT_FILE_EXTENSION;
bool isAssetServerEnabled();
public slots:
/// Called by NodeList to inform us a node has been added
void nodeAdded(SharedNodePointer node);

View file

@ -2,88 +2,88 @@ name = mannequin
type = body+head
scale = 1
filename = mannequin/mannequin.baked.fbx
joint = jointRoot = Hips
joint = jointNeck = Neck
joint = jointLean = Spine
joint = jointLeftHand = LeftHand
joint = jointHead = Head
joint = jointEyeLeft = LeftEye
joint = jointEyeRight = RightEye
joint = jointRoot = Hips
joint = jointLeftHand = LeftHand
joint = jointRightHand = RightHand
joint = jointNeck = Neck
joint = jointHead = Head
freeJoint = LeftArm
freeJoint = LeftForeArm
freeJoint = RightArm
freeJoint = RightForeArm
bs = EyeBlink_L = blink = 1
bs = JawOpen = mouth_Open = 1
bs = LipsFunnel = Oo = 1
bs = BrowsU_L = brow_Up = 1
jointIndex = RightHandPinky2 = 19
jointIndex = LeftHandMiddle4 = 61
jointIndex = LeftHand = 41
jointIndex = LeftHandRing4 = 49
jointIndex = RightHandMiddle3 = 36
jointIndex = LeftHandThumb4 = 57
jointIndex = RightToe_End = 10
jointIndex = LeftHandRing1 = 46
jointIndex = LeftForeArm = 40
jointIndex = RightHandIndex4 = 29
jointIndex = LeftShoulder = 38
jointIndex = RightHandMiddle4 = 37
jointIndex = RightShoulder = 14
jointIndex = LeftLeg = 2
jointIndex = LeftToe_End = 5
jointIndex = Hips = 0
jointIndex = RightFoot = 8
jointIndex = RightHandThumb2 = 31
jointIndex = LeftHandMiddle3 = 60
jointIndex = RightHandThumb1 = 30
jointIndex = Neck = 62
jointIndex = Spine = 11
jointIndex = RightHandThumb4 = 33
jointIndex = RightHandMiddle1 = 34
jointIndex = LeftHandIndex4 = 53
jointIndex = face = 68
jointIndex = RightHandRing3 = 24
jointIndex = LeftHandPinky4 = 45
jointIndex = LeftHandMiddle2 = 59
jointIndex = RightHandThumb3 = 32
bs = EyeBlink_L = blink = 1
jointIndex = LeftHandPinky3 = 44
jointIndex = HeadTop_End = 66
jointIndex = Spine1 = 12
jointIndex = LeftHandRing3 = 48
jointIndex = mannequin1 = 67
jointIndex = RightEye = 65
jointIndex = RightHandRing4 = 25
jointIndex = RightHandPinky4 = 21
jointIndex = LeftHandRing2 = 47
jointIndex = RightHandIndex3 = 28
jointIndex = RightUpLeg = 6
jointIndex = LeftArm = 39
jointIndex = LeftHandThumb3 = 56
jointIndex = RightHandIndex2 = 27
jointIndex = RightForeArm = 16
jointIndex = RightArm = 15
jointIndex = RightHandRing2 = 23
jointIndex = LeftHandMiddle1 = 58
jointIndex = Spine2 = 13
jointIndex = LeftHandThumb2 = 55
jointIndex = RightHandMiddle2 = 35
jointIndex = RightHandPinky1 = 18
jointIndex = LeftUpLeg = 1
jointIndex = RightLeg = 7
jointIndex = LeftHandIndex2 = 51
jointIndex = LeftHand = 41
jointIndex = RightHandMiddle1 = 34
jointIndex = LeftHandPinky4 = 45
jointIndex = RightHand = 17
jointIndex = LeftHandIndex3 = 52
jointIndex = LeftFoot = 3
jointIndex = RightHandPinky3 = 20
jointIndex = RightHandIndex1 = 26
jointIndex = LeftHandPinky1 = 42
jointIndex = RightToeBase = 9
jointIndex = LeftHandIndex1 = 50
jointIndex = LeftToeBase = 4
jointIndex = LeftHandPinky2 = 43
jointIndex = RightHandRing1 = 22
jointIndex = LeftHandThumb1 = 54
jointIndex = LeftEye = 64
jointIndex = LeftFoot = 3
jointIndex = Head = 63
jointIndex = Spine1 = 12
jointIndex = RightHandRing4 = 25
jointIndex = RightHandPinky1 = 18
jointIndex = LeftHandIndex1 = 50
jointIndex = RightHandIndex3 = 28
jointIndex = LeftHandIndex3 = 52
jointIndex = LeftToe_End = 5
jointIndex = RightArm = 15
jointIndex = RightHandRing3 = 24
jointIndex = RightHandThumb2 = 31
jointIndex = Spine2 = 13
jointIndex = HeadTop_End = 66
jointIndex = LeftToeBase = 4
jointIndex = RightUpLeg = 6
jointIndex = RightForeArm = 16
jointIndex = LeftHandMiddle1 = 58
jointIndex = LeftHandRing3 = 48
jointIndex = RightHandPinky4 = 21
jointIndex = RightHandIndex1 = 26
jointIndex = Hips = 0
jointIndex = RightEye = 65
jointIndex = RightHandPinky2 = 19
jointIndex = LeftHandMiddle2 = 59
jointIndex = LeftHandPinky1 = 42
jointIndex = LeftHandRing4 = 49
jointIndex = RightFoot = 8
jointIndex = RightHandIndex2 = 27
jointIndex = RightToe_End = 10
jointIndex = RightHandThumb3 = 32
jointIndex = LeftHandMiddle3 = 60
jointIndex = LeftHandThumb4 = 57
jointIndex = LeftHandMiddle4 = 61
jointIndex = LeftHandThumb1 = 54
jointIndex = LeftHandThumb3 = 56
jointIndex = body = 67
jointIndex = LeftArm = 39
jointIndex = RightToeBase = 9
jointIndex = LeftEye = 64
jointIndex = RightLeg = 7
jointIndex = face = 68
jointIndex = LeftForeArm = 40
jointIndex = RightHandThumb4 = 33
jointIndex = RightHandRing1 = 22
jointIndex = LeftUpLeg = 1
jointIndex = LeftHandPinky2 = 43
jointIndex = LeftLeg = 2
jointIndex = LeftHandIndex4 = 53
jointIndex = RightHandThumb1 = 30
jointIndex = LeftHandRing2 = 47
jointIndex = RightHandMiddle2 = 35
jointIndex = RightHandMiddle3 = 36
jointIndex = Spine = 11
jointIndex = RightHandMiddle4 = 37
jointIndex = LeftHandIndex2 = 51
jointIndex = RightHandRing2 = 23
jointIndex = LeftHandThumb2 = 55
jointIndex = LeftShoulder = 38
jointIndex = Neck = 62
jointIndex = RightHandIndex4 = 29
jointIndex = LeftHandRing1 = 46
jointIndex = RightShoulder = 14

Binary file not shown.

After

(image error) Size: 22 KiB

Binary file not shown.

After

(image error) Size: 1.1 MiB

Binary file not shown.

After

(image error) Size: 1,002 KiB

Binary file not shown.

After

(image error) Size: 525 KiB

View file

@ -165,11 +165,11 @@ TextField {
anchors.left: parent.left
Binding on anchors.right {
when: parent.right
value: parent.right
when: textField.right
value: textField.right
}
Binding on wrapMode {
when: parent.right
when: textField.right
value: Text.WordWrap
}

View file

@ -16,10 +16,11 @@ import QtQuick 2.5
import QtGraphicalEffects 1.0
import "toolbars"
import "../styles-uit"
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
Column {
id: root;
visible: false;
visible: !!suggestions.count;
property int cardWidth: 212;
property int cardHeight: 152;
@ -32,21 +33,37 @@ Column {
property int stackedCardShadowHeight: 4;
property int labelSize: 20;
property string metaverseServerUrl: '';
property string protocol: '';
property string actions: 'snapshot';
// sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick.
property string labelText: actions;
property string filter: '';
onFilterChanged: filterChoicesByText();
property var goFunction: null;
property var rpc: null;
property var http: null;
HifiConstants { id: hifi }
ListModel { id: suggestions; }
Component.onCompleted: suggestions.getFirstPage();
HifiModels.PSFListModel {
id: suggestions;
http: root.http;
property var options: [
'include_actions=' + actions,
'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
'require_online=true',
'protocol=' + encodeURIComponent(Window.protocolSignature())
];
endpoint: '/api/v1/user_stories?' + options.join('&');
itemsPerPage: 3;
processPage: function (data) {
return data.user_stories.map(makeModelData);
};
listModelName: actions;
listView: scroll;
searchFilter: filter;
}
function resolveUrl(url) {
return (url.indexOf('/') === 0) ? (metaverseServerUrl + url) : url;
return (url.indexOf('/') === 0) ? (Account.metaverseServerURL + url) : url;
}
function makeModelData(data) { // create a new obj from data
// ListModel elements will only ever have those properties that are defined by the first obj that is added.
@ -55,16 +72,11 @@ Column {
tags = data.tags || [data.action, data.username],
description = data.description || "",
thumbnail_url = data.thumbnail_url || "";
if (actions === 'concurrency,snapshot') {
// A temporary hack for simulating announcements. We won't use this in production, but if requested, we'll use this data like announcements.
data.details.connections = 4;
data.action = 'announcement';
}
return {
place_name: name,
username: data.username || "",
path: data.path || "",
created_at: data.created_at || "",
created_at: data.created_at || data.updated_at || "", // FIXME why aren't we getting created_at?
action: data.action || "",
thumbnail_url: resolveUrl(thumbnail_url),
image_url: resolveUrl(data.details && data.details.image_url),
@ -74,125 +86,9 @@ Column {
tags: tags,
description: description,
online_users: data.details.connections || data.details.concurrency || 0,
drillDownToPlace: false,
searchText: [name].concat(tags, description || []).join(' ').toUpperCase()
}
}
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 = [
'now=' + new Date().toISOString(),
'include_actions=' + actions,
'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
'require_online=true',
'protocol=' + protocol,
'page=' + pageNumber
];
var url = metaverseBase + 'user_stories?' + options.join('&');
var thisRequestId = ++requestId;
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));
if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now
if ((pageNumber === 1) && cb1) {
cb1();
}
return getUserStoryPage(pageNumber + 1, cb);
}
cb();
});
}
function fillDestinations() { // Public
console.debug('Feed::fillDestinations()')
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);
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) {
counter++;
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) {
// 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) {
return suggestable(story);
}
return words.every(function (word) {
return story.searchText.indexOf(word) >= 0;
});
}
function addToSuggestions(place) {
var collapse = ((actions === 'concurrency,snapshot') && (place.action !== 'concurrency')) || (place.action === 'announcement');
if (collapse) {
var existing = placeMap[place.place_name];
if (existing) {
existing.drillDownToPlace = true;
return;
}
}
suggestions.append(place);
if (collapse) {
placeMap[place.place_name] = suggestions.get(suggestions.count - 1);
} else if (place.action === 'concurrency') {
suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories).
}
}
return function (story) {
if (matches(story)) {
addToSuggestions(story);
}
drillDownToPlace: false
};
}
function filterChoicesByText() {
suggestions.clear();
placeMap = {};
allStories.forEach(makeFilteredStoryProcessor());
root.visible = !!suggestions.count;
}
RalewayBold {
id: label;
@ -208,6 +104,7 @@ Column {
highlightMoveDuration: -1;
highlightMoveVelocity: -1;
currentIndex: -1;
onAtXEndChanged: { if (scroll.atXEnd && !scroll.atXBeginning) { suggestions.getNextPage(); } }
spacing: 12;
width: parent.width;
@ -239,21 +136,4 @@ Column {
unhoverThunk: function () { hovered = false }
}
}
NumberAnimation {
id: anim;
target: scroll;
property: "contentX";
duration: 250;
}
function scrollToIndex(index) {
anim.running = false;
var pos = scroll.contentX;
var destPos;
scroll.positionViewAtIndex(index, ListView.Contain);
destPos = scroll.contentX;
anim.from = pos;
anim.to = destPos;
scroll.currentIndex = index;
anim.running = true;
}
}

View file

@ -18,6 +18,7 @@ import Qt.labs.settings 1.0
import "../styles-uit"
import "../controls-uit" as HifiControlsUit
import "../controls" as HifiControls
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
// references HMD, Users, UserActivityLogger from root context
@ -37,13 +38,42 @@ Rectangle {
property var myData: ({profileUrl: "", displayName: "", userName: "", audioLevel: 0.0, avgAudioLevel: 0.0, admin: true, placeName: "", connection: "", isPresent: true}); // valid dummy until set
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
property var nearbyUserModelData: []; // This simple list is essentially a mirror of the nearbyUserModel listModel without all the extra complexities.
property var connectionsUserModelData: []; // This simple list is essentially a mirror of the connectionsUserModel listModel without all the extra complexities.
property bool iAmAdmin: false;
property var activeTab: "nearbyTab";
property bool currentlyEditingDisplayName: false
property bool punctuationMode: false;
HifiConstants { id: hifi; }
RootHttpRequest { id: http; }
HifiModels.PSFListModel {
id: connectionsUserModel;
http: http;
endpoint: "/api/v1/users?filter=connections";
property var sortColumn: connectionsTable.getColumn(connectionsTable.sortIndicatorColumn);
sortProperty: switch (sortColumn && sortColumn.role) {
case 'placeName':
'location';
break;
case 'connection':
'is_friend';
break;
default:
'username';
}
sortAscending: connectionsTable.sortIndicatorOrder === Qt.AscendingOrder;
itemsPerPage: 9;
listView: connectionsTable;
processPage: function (data) {
return data.users.map(function (user) {
return {
userName: user.username,
connection: user.connection,
profileUrl: user.images.thumbnail,
placeName: (user.location.root || user.location.domain || {}).name || ''
};
});
};
}
// The letterbox used for popup messages
LetterboxMessage {
@ -106,16 +136,6 @@ Rectangle {
});
return sessionIDs;
}
function getSelectedConnectionsUserNames() {
var userNames = [];
connectionsTable.selection.forEach(function (userIndex) {
var datum = connectionsUserModelData[userIndex];
if (datum) {
userNames.push(datum.userName);
}
});
return userNames;
}
function refreshNearbyWithFilter() {
// We should just be able to set settings.filtered to inViewCheckbox.checked, but see #3249, so send to .js for saving.
var userIds = getSelectedNearbySessionIDs();
@ -232,9 +252,7 @@ Rectangle {
anchors.fill: parent;
onClicked: {
if (activeTab != "connectionsTab") {
connectionsLoading.visible = false;
connectionsLoading.visible = true;
pal.sendToScript({method: 'refreshConnections'});
connectionsUserModel.getFirstPage();
}
activeTab = "connectionsTab";
connectionsHelpText.color = hifi.colors.blueAccent;
@ -258,11 +276,7 @@ Rectangle {
id: reloadConnections;
width: reloadConnections.height;
glyph: hifi.glyphs.reload;
onClicked: {
connectionsLoading.visible = false;
connectionsLoading.visible = true;
pal.sendToScript({method: 'refreshConnections'});
}
onClicked: connectionsUserModel.getFirstPage('delayRefresh');
}
}
// "CONNECTIONS" text
@ -472,7 +486,7 @@ Rectangle {
visible: !isCheckBox && !isButton && !isAvgAudio;
uuid: model ? model.sessionId : "";
selected: styleData.selected;
isReplicated: model.isReplicated;
isReplicated: model && model.isReplicated;
isAdmin: model && model.admin;
isPresent: model && model.isPresent;
// Size
@ -702,7 +716,7 @@ Rectangle {
anchors.top: parent.top;
anchors.topMargin: 185;
anchors.horizontalCenter: parent.horizontalCenter;
visible: true;
visible: !connectionsUserModel.retrievedAtLeastOnePage;
onVisibleChanged: {
if (visible) {
connectionsTimeoutTimer.start();
@ -747,14 +761,6 @@ Rectangle {
headerVisible: true;
sortIndicatorColumn: settings.connectionsSortIndicatorColumn;
sortIndicatorOrder: settings.connectionsSortIndicatorOrder;
onSortIndicatorColumnChanged: {
settings.connectionsSortIndicatorColumn = sortIndicatorColumn;
sortConnectionsModel();
}
onSortIndicatorOrderChanged: {
settings.connectionsSortIndicatorOrder = sortIndicatorOrder;
sortConnectionsModel();
}
TableViewColumn {
id: connectionsUserNameHeader;
@ -779,8 +785,14 @@ Rectangle {
resizable: false;
}
model: ListModel {
id: connectionsUserModel;
model: connectionsUserModel;
Connections {
target: connectionsTable.flickableItem;
onAtYEndChanged: {
if (connectionsTable.flickableItem.atYEnd && !connectionsTable.flickableItem.atYBeginning) {
connectionsUserModel.getNextPage();
}
}
}
// This Rectangle refers to each Row in the connectionsTable.
@ -859,12 +871,9 @@ Rectangle {
checked: model && (model.connection === "friend");
boxSize: 24;
onClicked: {
var newValue = model.connection !== "friend";
connectionsUserModel.setProperty(model.userIndex, styleData.role, (newValue ? "friend" : "connection"));
connectionsUserModelData[model.userIndex][styleData.role] = newValue; // Defensive programming
pal.sendToScript({method: newValue ? 'addFriend' : 'removeFriend', params: model.userName});
pal.sendToScript({method: checked ? 'addFriend' : 'removeFriend', params: model.userName});
UserActivityLogger["palAction"](newValue ? styleData.role : "un-" + styleData.role, model.sessionId);
UserActivityLogger["palAction"](checked ? styleData.role : "un-" + styleData.role, model.sessionId);
}
}
}
@ -1130,16 +1139,6 @@ Rectangle {
sortModel();
reloadNearby.color = 0;
break;
case 'connections':
var data = message.params;
if (pal.debug) {
console.log('Got connection data: ', JSON.stringify(data));
}
connectionsUserModelData = data;
sortConnectionsModel();
connectionsLoading.visible = false;
connectionsRefreshProblemText.visible = false;
break;
case 'select':
var sessionIds = message.params[0];
var selected = message.params[1];
@ -1239,6 +1238,14 @@ Rectangle {
reloadNearby.color = 2;
}
break;
case 'inspectionCertificate_resetCert':
// marketplaces.js sends out a signal to QML with that method when the tablet screen changes and it's not changed to a commerce-related screen.
// We want it to only be handled by the InspectionCertificate.qml, but there's not an easy way of doing that.
// As a part of a "cleanup inspectionCertificate_resetCert" ticket, we'll have to figure out less logspammy way of doing what has to be done.
break;
case 'http.response':
http.handleHttpResponse(message);
break;
default:
console.log('Unrecognized message:', JSON.stringify(message));
}
@ -1287,45 +1294,6 @@ Rectangle {
nearbyTable.positionViewAtRow(newSelectedIndexes[0], ListView.Beginning);
}
}
function sortConnectionsModel() {
var column = connectionsTable.getColumn(connectionsTable.sortIndicatorColumn);
var sortProperty = column ? column.role : "userName";
var before = (connectionsTable.sortIndicatorOrder === Qt.AscendingOrder) ? -1 : 1;
var after = -1 * before;
// get selection(s) before sorting
var selectedIDs = getSelectedConnectionsUserNames();
connectionsUserModelData.sort(function (a, b) {
var aValue = a[sortProperty].toString().toLowerCase(), bValue = b[sortProperty].toString().toLowerCase();
if (!aValue && !bValue) {
return 0;
} else if (!aValue) {
return after;
} else if (!bValue) {
return before;
}
switch (true) {
case (aValue < bValue): return before;
case (aValue > bValue): return after;
default: return 0;
}
});
connectionsTable.selection.clear();
connectionsUserModel.clear();
var userIndex = 0;
var newSelectedIndexes = [];
connectionsUserModelData.forEach(function (datum) {
datum.userIndex = userIndex++;
connectionsUserModel.append(datum);
if (selectedIDs.indexOf(datum.sessionId) != -1) {
newSelectedIndexes.push(datum.userIndex);
}
});
if (newSelectedIndexes.length > 0) {
connectionsTable.selection.select(newSelectedIndexes);
connectionsTable.positionViewAtRow(newSelectedIndexes[0], ListView.Beginning);
}
}
signal sendToScript(var message);
function noticeSelection() {
var userIds = [];

View file

@ -0,0 +1,39 @@
//
// RootHttpRequest.qml
// qml/hifi
//
// Create an item of this in the ROOT qml to be able to make http requests.
// Used by PSFListModel.qml
//
// Created by Howard Stearns on 5/29/2018
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
Item {
property var httpCalls: ({});
property var httpCounter: 0;
// Public function for initiating an http request.
// REQUIRES parent to be root to have sendToScript!
function request(options, callback) {
console.debug('HttpRequest', JSON.stringify(options));
httpCalls[httpCounter] = callback;
var message = {method: 'http.request', params: options, id: httpCounter++, jsonrpc: "2.0"};
parent.sendToScript(message);
}
// REQUIRES that parent/root handle http.response message.method in fromScript, by calling this function.
function handleHttpResponse(message) {
var callback = httpCalls[message.id]; // FIXME: as different top level tablet apps gets loaded, the id repeats. We should drop old app callbacks without warning.
if (!callback) {
console.warn('No callback for', JSON.stringify(message));
return;
}
delete httpCalls[message.id];
console.debug('HttpRequest response', JSON.stringify(message));
callback(message.error, message.response);
}
}

View file

@ -42,7 +42,7 @@ Rectangle {
property bool alreadyOwned: false;
property int itemPrice: -1;
property bool isCertified;
property string itemType;
property string itemType: "unknown";
property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar", "unknown"];
property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar", "item"];
property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR", "REZ"];
@ -98,9 +98,6 @@ Rectangle {
} else {
root.certificateId = result.data.certificate_id;
root.itemHref = result.data.download_url;
if (result.data.categories.indexOf("Wearables") > -1) {
root.itemType = "wearable";
}
root.activeView = "checkoutSuccess";
UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned);
}
@ -170,9 +167,6 @@ Rectangle {
root.activeView = "checkoutFailure";
} else {
root.itemHref = result.data.download_url;
if (result.data.categories.indexOf("Wearables") > -1) {
root.itemType = "wearable";
}
root.activeView = "checkoutSuccess";
}
}
@ -186,20 +180,6 @@ Rectangle {
itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg";
}
onItemHrefChanged: {
if (root.itemHref.indexOf(".fst") > -1) {
root.itemType = "avatar";
} else if (root.itemHref.indexOf('.json.gz') > -1 || root.itemHref.indexOf('.content.zip') > -1) {
root.itemType = "contentSet";
} else if (root.itemHref.indexOf('.app.json') > -1) {
root.itemType = "app";
} else if (root.itemHref.indexOf('.json') > -1) {
root.itemType = "entity"; // "wearable" type handled later
} else {
root.itemType = "unknown";
}
}
onItemTypeChanged: {
if (root.itemType === "entity" || root.itemType === "wearable" ||
root.itemType === "contentSet" || root.itemType === "avatar" || root.itemType === "app") {
@ -1102,6 +1082,7 @@ Rectangle {
root.referrer = message.params.referrer;
root.itemAuthor = message.params.itemAuthor;
root.itemEdition = message.params.itemEdition || -1;
root.itemType = message.params.itemType || "unknown";
refreshBuyUI();
break;
default:

View file

@ -44,7 +44,7 @@ Item {
Item {
id: avatarImage;
visible: profileUrl !== "" && userName !== "";
visible: profilePicUrl !== "" && userName !== "";
// Size
anchors.verticalCenter: parent.verticalCenter;
anchors.left: parent.left;

View file

@ -19,6 +19,7 @@ import "../../../../styles-uit"
import "../../../../controls-uit" as HifiControlsUit
import "../../../../controls" as HifiControls
import "../" as HifiCommerceCommon
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
Item {
HifiConstants { id: hifi; }
@ -36,6 +37,8 @@ Item {
property string assetName: "";
property string assetCertID: "";
property string sendingPubliclyEffectImage;
property var http;
property var listModelName;
// This object is always used in a popup or full-screen Wallet section.
// This MouseArea is used to prevent a user from being
@ -118,9 +121,7 @@ Item {
if (root.currentActiveView === 'chooseRecipientConnection') {
// Refresh connections model
connectionsLoading.visible = false;
connectionsLoading.visible = true;
sendSignalToParent({method: 'refreshConnections'});
connectionsModel.getFirstPage();
} else if (root.currentActiveView === 'sendAssetHome') {
Commerce.balance();
} else if (root.currentActiveView === 'chooseRecipientNearby') {
@ -392,11 +393,17 @@ Item {
hoverEnabled: true;
}
ListModel {
HifiModels.PSFListModel {
id: connectionsModel;
}
ListModel {
id: filteredConnectionsModel;
http: root.http;
listModelName: root.listModelName;
endpoint: "/api/v1/users?filter=connections";
itemsPerPage: 8;
listView: connectionsList;
processPage: function (data) {
return data.users;
};
searchFilter: filterBar.text;
}
Rectangle {
@ -472,10 +479,6 @@ Item {
anchors.fill: parent;
centerPlaceholderGlyph: hifi.glyphs.search;
onTextChanged: {
buildFilteredConnectionsModel();
}
onAccepted: {
focus = false;
}
@ -495,6 +498,7 @@ Item {
AnimatedImage {
id: connectionsLoading;
visible: !connectionsModel.retrievedAtLeastOnePage;
source: "../../../../../icons/profilePicLoading.gif"
width: 120;
height: width;
@ -515,14 +519,15 @@ Item {
}
visible: !connectionsLoading.visible;
clip: true;
model: filteredConnectionsModel;
model: connectionsModel;
onAtYEndChanged: if (connectionsList.atYEnd && !connectionsList.atYBeginning) { connectionsModel.getNextPage(); }
snapMode: ListView.SnapToItem;
// Anchors
anchors.fill: parent;
delegate: ConnectionItem {
isSelected: connectionsList.currentIndex === index;
userName: model.userName;
profilePicUrl: model.profileUrl;
userName: model.username;
profilePicUrl: model.images.thumbnail;
anchors.topMargin: 6;
anchors.bottomMargin: 6;
@ -553,7 +558,7 @@ Item {
// "Make a Connection" instructions
Rectangle {
id: connectionInstructions;
visible: connectionsModel.count === 0 && !connectionsLoading.visible;
visible: connectionsModel.count === 0 && !connectionsModel.searchFilter && !connectionsLoading.visible;
anchors.fill: parent;
color: "white";
@ -1806,22 +1811,6 @@ Item {
// FUNCTION DEFINITIONS START
//
function updateConnections(connections) {
connectionsModel.clear();
connectionsModel.append(connections);
buildFilteredConnectionsModel();
connectionsLoading.visible = false;
}
function buildFilteredConnectionsModel() {
filteredConnectionsModel.clear();
for (var i = 0; i < connectionsModel.count; i++) {
if (connectionsModel.get(i).userName.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
filteredConnectionsModel.append(connectionsModel.get(i));
}
}
}
function resetSendAssetData() {
amountTextField.focus = false;
optionalMessage.focus = false;

View file

@ -163,7 +163,6 @@ Item {
Rectangle {
id: contextCard;
z: 2;
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.top: parent.top;
@ -337,7 +336,6 @@ Item {
Rectangle {
id: permissionExplanationCard;
z: 1;
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.top: parent.top;
@ -596,8 +594,8 @@ Item {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
contextCard.z = 1;
permissionExplanationCard.z = 0;
contextCard.visible = true;
permissionExplanationCard.visible = false;
root.sendToPurchases({ method: 'flipCard' });
}
onEntered: {
@ -779,8 +777,8 @@ Item {
noPermissionGlyph.color = hifi.colors.redAccent;
}
onClicked: {
contextCard.z = 0;
permissionExplanationCard.z = 1;
contextCard.visible = false;
permissionExplanationCard.visible = true;
root.sendToPurchases({ method: 'flipCard' });
}
}

View file

@ -16,10 +16,12 @@ import QtQuick 2.5
import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
import "../wallet" as HifiWallet
import "../common" as HifiCommerceCommon
import "../inspectionCertificate" as HifiInspectionCertificate
import "../common/sendAsset" as HifiSendAsset
import "../.." as HifiCommon
// references XXX from root context
@ -34,12 +36,17 @@ Rectangle {
property bool punctuationMode: false;
property bool isShowingMyItems: false;
property bool isDebuggingFirstUseTutorial: false;
property int pendingItemCount: 0;
property string installedApps;
property bool keyboardRaised: false;
property int numUpdatesAvailable: 0;
// Style
color: hifi.colors.white;
function getPurchases() {
root.activeView = "purchasesMain";
root.installedApps = Commerce.getInstalledApps();
purchasesModel.getFirstPage();
Commerce.getAvailableUpdates();
}
Connections {
target: Commerce;
@ -62,10 +69,7 @@ Rectangle {
if ((Settings.getValue("isFirstUseOfPurchases", true) || root.isDebuggingFirstUseTutorial) && root.activeView !== "firstUseTutorial") {
root.activeView = "firstUseTutorial";
} else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") {
root.activeView = "purchasesMain";
root.installedApps = Commerce.getInstalledApps();
Commerce.inventory();
Commerce.getAvailableUpdates();
getPurchases();
}
} else {
console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus);
@ -81,39 +85,7 @@ Rectangle {
}
onInventoryResult: {
purchasesReceived = true;
if (result.status !== 'success') {
console.log("Failed to get purchases", result.message);
} else if (!purchasesContentsList.dragging) { // Don't modify the view if the user's scrolling
var inventoryResult = processInventoryResult(result.data.assets);
var currentIndex = purchasesContentsList.currentIndex === -1 ? 0 : purchasesContentsList.currentIndex;
purchasesModel.clear();
purchasesModel.append(inventoryResult);
root.pendingItemCount = 0;
for (var i = 0; i < purchasesModel.count; i++) {
if (purchasesModel.get(i).status === "pending") {
root.pendingItemCount++;
}
}
if (previousPurchasesModel.count !== 0) {
checkIfAnyItemStatusChanged();
} else {
// Fill statusChanged default value
// Not doing this results in the default being true...
for (var i = 0; i < purchasesModel.count; i++) {
purchasesModel.setProperty(i, "statusChanged", false);
}
}
previousPurchasesModel.append(inventoryResult);
buildFilteredPurchasesModel();
purchasesContentsList.positionViewAtIndex(currentIndex, ListView.Beginning);
}
purchasesModel.handlePage(result.status !== "success" && result.message, result);
}
onAvailableUpdatesResult: {
@ -134,6 +106,10 @@ Rectangle {
}
}
onIsShowingMyItemsChanged: {
getPurchases();
}
Timer {
id: notSetUpTimer;
interval: 200;
@ -172,8 +148,14 @@ Rectangle {
}
}
HifiCommon.RootHttpRequest {
id: http;
}
HifiSendAsset.SendAsset {
id: sendAsset;
http: http;
listModelName: "Gift Connections";
z: 998;
visible: root.activeView === "giftAsset";
anchors.fill: parent;
@ -183,9 +165,7 @@ Rectangle {
Connections {
onSendSignalToParent: {
if (msg.method === 'sendAssetHome_back' || msg.method === 'closeSendAsset') {
root.activeView = "purchasesMain";
Commerce.inventory();
Commerce.getAvailableUpdates();
getPurchases();
} else {
sendToScript(msg);
}
@ -449,10 +429,7 @@ Rectangle {
case 'tutorial_skipClicked':
case 'tutorial_finished':
Settings.setValue("isFirstUseOfPurchases", false);
root.activeView = "purchasesMain";
root.installedApps = Commerce.getInstalledApps();
Commerce.inventory();
Commerce.getAvailableUpdates();
getPurchases();
break;
}
}
@ -528,7 +505,7 @@ Rectangle {
},
{
"displayName": "Content Set",
"filterName": "contentSet"
"filterName": "content_set"
},
{
"displayName": "Entity",
@ -540,7 +517,7 @@ Rectangle {
},
{
"displayName": "Updatable",
"filterName": "updatable"
"filterName": "updated"
}
]
filterBar.primaryFilterChoices.clear();
@ -548,14 +525,12 @@ Rectangle {
}
onPrimaryFilter_displayNameChanged: {
buildFilteredPurchasesModel();
purchasesContentsList.positionViewAtIndex(0, ListView.Beginning)
purchasesModel.tagsFilter = filterBar.primaryFilter_filterName;
filterBar.previousPrimaryFilter = filterBar.primaryFilter_displayName;
}
onTextChanged: {
buildFilteredPurchasesModel();
purchasesContentsList.positionViewAtIndex(0, ListView.Beginning)
purchasesModel.searchFilter = filterBar.text;
filterBar.previousText = filterBar.text;
}
}
@ -574,24 +549,41 @@ Rectangle {
anchors.topMargin: 16;
}
ListModel {
HifiModels.PSFListModel {
id: purchasesModel;
}
ListModel {
id: previousPurchasesModel;
}
HifiCommerceCommon.SortableListModel {
id: tempPurchasesModel;
}
HifiCommerceCommon.SortableListModel {
id: filteredPurchasesModel;
itemsPerPage: 6;
listModelName: 'purchases';
getPage: function () {
console.debug('getPage', purchasesModel.listModelName, root.isShowingMyItems, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage);
Commerce.inventory(
root.isShowingMyItems ? "proofs" : "purchased",
filterBar.primaryFilter_filterName,
filterBar.text,
purchasesModel.currentPageToRetrieve,
purchasesModel.itemsPerPage
);
}
processPage: function(data) {
purchasesReceived = true; // HRS FIXME?
data.assets.forEach(function (item) {
if (item.status.length > 1) { console.warn("Unrecognized inventory status", item); }
item.status = item.status[0];
item.categories = item.categories.join(';');
item.cardBackVisible = false;
item.isInstalled = root.installedApps.indexOf(item.id) > -1;
item.wornEntityID = '';
});
sendToScript({ method: 'purchases_updateWearables' });
return data.assets;
}
}
ListView {
id: purchasesContentsList;
visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0);
visible: purchasesModel.count !== 0;
clip: true;
model: filteredPurchasesModel;
model: purchasesModel;
snapMode: ListView.SnapToItem;
// Anchors
anchors.top: separator.bottom;
@ -608,13 +600,13 @@ Rectangle {
itemEdition: model.edition_number;
numberSold: model.number_sold;
limitedRun: model.limited_run;
displayedItemCount: model.displayedItemCount;
cardBackVisible: model.cardBackVisible;
isInstalled: model.isInstalled;
displayedItemCount: 999; // For now (and maybe longer), we're going to display all the edition numbers.
cardBackVisible: model.cardBackVisible || false;
isInstalled: model.isInstalled || false;
wornEntityID: model.wornEntityID;
upgradeUrl: model.upgrade_url;
upgradeTitle: model.upgrade_title;
itemType: model.itemType;
itemType: model.item_type;
isShowingMyItems: root.isShowingMyItems;
valid: model.valid;
anchors.topMargin: 10;
@ -706,11 +698,11 @@ Rectangle {
} else if (msg.method === "setFilterText") {
filterBar.text = msg.filterText;
} else if (msg.method === "flipCard") {
for (var i = 0; i < filteredPurchasesModel.count; i++) {
for (var i = 0; i < purchasesModel.count; i++) {
if (i !== index || msg.closeAll) {
filteredPurchasesModel.setProperty(i, "cardBackVisible", false);
purchasesModel.setProperty(i, "cardBackVisible", false);
} else {
filteredPurchasesModel.setProperty(i, "cardBackVisible", true);
purchasesModel.setProperty(i, "cardBackVisible", true);
}
}
} else if (msg.method === "updateItemClicked") {
@ -761,7 +753,7 @@ Rectangle {
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = function() {
Entities.deleteEntity(msg.wornEntityID);
filteredPurchasesModel.setProperty(index, 'wornEntityID', '');
purchasesModel.setProperty(index, 'wornEntityID', '');
root.activeView = "giftAsset";
lightboxPopup.visible = false;
};
@ -773,6 +765,14 @@ Rectangle {
}
}
}
onAtYEndChanged: {
if (purchasesContentsList.atYEnd && !purchasesContentsList.atYBeginning) {
console.log("User scrolled to the bottom of 'Purchases'.");
purchasesModel.getNextPage();
}
}
}
Rectangle {
@ -953,146 +953,14 @@ Rectangle {
//
// FUNCTION DEFINITIONS START
//
function processInventoryResult(inventory) {
for (var i = 0; i < inventory.length; i++) {
if (inventory[i].status.length > 1) {
console.log("WARNING: Inventory result index " + i + " has a status of length >1!")
}
inventory[i].status = inventory[i].status[0];
inventory[i].categories = inventory[i].categories.join(';');
}
return inventory;
}
function populateDisplayedItemCounts() {
var itemCountDictionary = {};
var currentItemId;
for (var i = 0; i < filteredPurchasesModel.count; i++) {
currentItemId = filteredPurchasesModel.get(i).id;
if (itemCountDictionary[currentItemId] === undefined) {
itemCountDictionary[currentItemId] = 1;
} else {
itemCountDictionary[currentItemId]++;
}
}
for (var i = 0; i < filteredPurchasesModel.count; i++) {
filteredPurchasesModel.setProperty(i, "displayedItemCount", itemCountDictionary[filteredPurchasesModel.get(i).id]);
}
}
function sortByDate() {
filteredPurchasesModel.sortColumnName = "purchase_date";
filteredPurchasesModel.isSortingDescending = true;
filteredPurchasesModel.valuesAreNumerical = true;
filteredPurchasesModel.quickSort();
}
function buildFilteredPurchasesModel() {
var sameItemCount = 0;
tempPurchasesModel.clear();
for (var i = 0; i < purchasesModel.count; i++) {
if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) {
tempPurchasesModel.insert(0, purchasesModel.get(i));
} else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") ||
(!root.isShowingMyItems && purchasesModel.get(i).edition_number !== "0")) {
tempPurchasesModel.append(purchasesModel.get(i));
}
}
}
// primaryFilter filtering and adding of itemType property to model
var currentItemType, currentRootFileUrl, currentCategories;
for (var i = 0; i < tempPurchasesModel.count; i++) {
currentRootFileUrl = tempPurchasesModel.get(i).root_file_url;
currentCategories = tempPurchasesModel.get(i).categories;
if (currentRootFileUrl.indexOf(".fst") > -1) {
currentItemType = "avatar";
} else if (currentCategories.indexOf("Wearables") > -1) {
currentItemType = "wearable";
} else if (currentRootFileUrl.endsWith('.json.gz') || currentRootFileUrl.endsWith('.content.zip')) {
currentItemType = "contentSet";
} else if (currentRootFileUrl.endsWith('.app.json')) {
currentItemType = "app";
} else if (currentRootFileUrl.endsWith('.json')) {
currentItemType = "entity";
} else {
currentItemType = "unknown";
}
if (filterBar.primaryFilter_displayName !== "" &&
((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgrade_url === "") ||
(filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_filterName.toLowerCase() !== currentItemType.toLowerCase()))) {
tempPurchasesModel.remove(i);
i--;
} else {
tempPurchasesModel.setProperty(i, 'itemType', currentItemType);
}
}
for (var i = 0; i < tempPurchasesModel.count; i++) {
if (!filteredPurchasesModel.get(i)) {
sameItemCount = -1;
break;
} else if (tempPurchasesModel.get(i).itemId === filteredPurchasesModel.get(i).itemId &&
tempPurchasesModel.get(i).edition_number === filteredPurchasesModel.get(i).edition_number &&
tempPurchasesModel.get(i).status === filteredPurchasesModel.get(i).status) {
sameItemCount++;
}
}
if (sameItemCount !== tempPurchasesModel.count ||
filterBar.text !== filterBar.previousText ||
filterBar.primaryFilter !== filterBar.previousPrimaryFilter) {
filteredPurchasesModel.clear();
var currentId;
for (var i = 0; i < tempPurchasesModel.count; i++) {
currentId = tempPurchasesModel.get(i).id;
filteredPurchasesModel.append(tempPurchasesModel.get(i));
filteredPurchasesModel.setProperty(i, 'cardBackVisible', false);
filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1));
filteredPurchasesModel.setProperty(i, 'wornEntityID', '');
}
sendToScript({ method: 'purchases_updateWearables' });
populateDisplayedItemCounts();
sortByDate();
}
}
function checkIfAnyItemStatusChanged() {
var currentPurchasesModelId, currentPurchasesModelEdition, currentPurchasesModelStatus;
var previousPurchasesModelStatus;
for (var i = 0; i < purchasesModel.count; i++) {
currentPurchasesModelId = purchasesModel.get(i).id;
currentPurchasesModelEdition = purchasesModel.get(i).edition_number;
currentPurchasesModelStatus = purchasesModel.get(i).status;
for (var j = 0; j < previousPurchasesModel.count; j++) {
previousPurchasesModelStatus = previousPurchasesModel.get(j).status;
if (currentPurchasesModelId === previousPurchasesModel.get(j).id &&
currentPurchasesModelEdition === previousPurchasesModel.get(j).edition_number &&
currentPurchasesModelStatus !== previousPurchasesModelStatus) {
purchasesModel.setProperty(i, "statusChanged", true);
} else {
purchasesModel.setProperty(i, "statusChanged", false);
}
}
}
}
function updateCurrentlyWornWearables(wearables) {
for (var i = 0; i < filteredPurchasesModel.count; i++) {
for (var i = 0; i < purchasesModel.count; i++) {
for (var j = 0; j < wearables.length; j++) {
if (filteredPurchasesModel.get(i).itemType === "wearable" &&
wearables[j].entityCertID === filteredPurchasesModel.get(i).certificate_id &&
wearables[j].entityEdition.toString() === filteredPurchasesModel.get(i).edition_number) {
filteredPurchasesModel.setProperty(i, 'wornEntityID', wearables[j].entityID);
if (purchasesModel.get(i).itemType === "wearable" &&
wearables[j].entityCertID === purchasesModel.get(i).certificate_id &&
wearables[j].entityEdition.toString() === purchasesModel.get(i).edition_number) {
purchasesModel.setProperty(i, 'wornEntityID', wearables[j].entityID);
break;
}
}
@ -1149,7 +1017,7 @@ Rectangle {
switch (message.method) {
case 'updatePurchases':
referrerURL = message.referrerURL || "";
titleBarContainer.referrerURL = message.referrerURL;
titleBarContainer.referrerURL = message.referrerURL || "";
filterBar.text = message.filterText ? message.filterText : "";
break;
case 'inspectionCertificate_setCertificateId':
@ -1168,6 +1036,9 @@ Rectangle {
case 'updateWearables':
updateCurrentlyWornWearables(message.wornWearables);
break;
case 'http.response':
http.handleHttpResponse(message);
break;
default:
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
}

View file

@ -19,6 +19,7 @@ import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
import "../common" as HifiCommerceCommon
import "../common/sendAsset"
import "../.." as HifiCommon
Rectangle {
HifiConstants { id: hifi; }
@ -343,8 +344,14 @@ Rectangle {
}
}
HifiCommon.RootHttpRequest {
id: http;
}
SendAsset {
id: sendMoney;
http: http;
listModelName: "Send Money Connections";
z: 997;
visible: root.activeView === "sendMoney";
anchors.fill: parent;
@ -768,6 +775,13 @@ Rectangle {
case 'updateSelectedRecipientUsername':
sendMoney.fromScript(message);
break;
case 'http.response':
http.handleHttpResponse(message);
break;
case 'palIsStale':
case 'avatarDisconnected':
// Because we don't have "channels" for sending messages to a specific QML object, the messages are broadcast to all QML Items. If an Item of yours happens to be visible when some script sends a message with a method you don't expect, you'll get "Unrecognized message..." logs.
break;
default:
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
}

View file

@ -18,27 +18,17 @@ import QtQuick.Controls 2.2
import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
Item {
HifiConstants { id: hifi; }
id: root;
property bool initialHistoryReceived: false;
property bool historyRequestPending: true;
property bool noMoreHistoryData: false;
property int pendingCount: 0;
property int currentHistoryPage: 1;
property var pagesAlreadyAdded: new Array();
onVisibleChanged: {
if (visible) {
transactionHistoryModel.clear();
Commerce.balance();
initialHistoryReceived = false;
root.currentHistoryPage = 1;
root.noMoreHistoryData = false;
root.historyRequestPending = true;
Commerce.history(root.currentHistoryPage);
transactionHistoryModel.getFirstPage();
Commerce.getAvailableUpdates();
} else {
refreshTimer.stop();
@ -53,86 +43,7 @@ Item {
}
onHistoryResult : {
root.initialHistoryReceived = true;
root.historyRequestPending = false;
if (result.status === 'success') {
var currentPage = parseInt(result.current_page);
if (result.data.history.length === 0) {
root.noMoreHistoryData = true;
console.log("No more data to retrieve from Commerce.history() endpoint.")
} else if (root.currentHistoryPage === 1) {
var sameItemCount = 0;
tempTransactionHistoryModel.clear();
tempTransactionHistoryModel.append(result.data.history);
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
if (!transactionHistoryModel.get(i)) {
sameItemCount = -1;
break;
} else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type &&
tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) {
sameItemCount++;
}
}
if (sameItemCount !== tempTransactionHistoryModel.count) {
transactionHistoryModel.clear();
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
transactionHistoryModel.append(tempTransactionHistoryModel.get(i));
}
calculatePendingAndInvalidated();
}
} else {
if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) {
console.log("Page " + currentPage + " of history has already been added to the list.");
} else {
// First, add the history result to a temporary model
tempTransactionHistoryModel.clear();
tempTransactionHistoryModel.append(result.data.history);
// Make a note that we've already added this page to the model...
root.pagesAlreadyAdded.push(currentPage);
var insertionIndex = 0;
// If there's nothing in the model right now, we don't need to modify insertionIndex.
if (transactionHistoryModel.count !== 0) {
var currentIteratorPage;
// Search through the whole transactionHistoryModel and look for the insertion point.
// The insertion point is found when the result page from the server is less than
// the page that the current item came from, OR when we've reached the end of the whole model.
for (var i = 0; i < transactionHistoryModel.count; i++) {
currentIteratorPage = transactionHistoryModel.get(i).resultIsFromPage;
if (currentPage < currentIteratorPage) {
insertionIndex = i;
break;
} else if (i === transactionHistoryModel.count - 1) {
insertionIndex = i + 1;
break;
}
}
}
// Go through the results we just got back from the server, setting the "resultIsFromPage"
// property of those results and adding them to the main model.
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
tempTransactionHistoryModel.setProperty(i, "resultIsFromPage", currentPage);
transactionHistoryModel.insert(i + insertionIndex, tempTransactionHistoryModel.get(i))
}
calculatePendingAndInvalidated();
}
}
}
// Only auto-refresh if the user hasn't scrolled
// and there is more data to grab
if (transactionHistory.atYBeginning && !root.noMoreHistoryData) {
refreshTimer.start();
}
transactionHistoryModel.handlePage(null, result);
}
onAvailableUpdatesResult: {
@ -147,7 +58,7 @@ Item {
Connections {
target: GlobalServices
onMyUsernameChanged: {
transactionHistoryModel.clear();
transactionHistoryModel.resetModel();
usernameText.text = Account.username;
}
}
@ -235,9 +146,8 @@ Item {
onTriggered: {
if (transactionHistory.atYBeginning) {
console.log("Refreshing 1st Page of Recent Activity...");
root.historyRequestPending = true;
Commerce.balance();
Commerce.history(1);
transactionHistoryModel.getFirstPage("delayedClear");
}
}
}
@ -299,11 +209,42 @@ Item {
}
}
ListModel {
id: tempTransactionHistoryModel;
}
ListModel {
HifiModels.PSFListModel {
id: transactionHistoryModel;
listModelName: "transaction history"; // For debugging. Alternatively, we could specify endpoint for that purpose, even though it's not used directly.
itemsPerPage: 6;
getPage: function () {
console.debug('getPage', transactionHistoryModel.listModelName, transactionHistoryModel.currentPageToRetrieve);
Commerce.history(transactionHistoryModel.currentPageToRetrieve, transactionHistoryModel.itemsPerPage);
}
processPage: function (data) {
console.debug('processPage', transactionHistoryModel.listModelName, JSON.stringify(data));
var result, pending; // Set up or get the accumulator for pending.
if (transactionHistoryModel.currentPageToRetrieve == 1) {
pending = {transaction_type: "pendingCount", count: 0};
result = [pending];
} else {
pending = transactionHistoryModel.get(0);
result = [];
}
// Either add to pending, or to result.
// Note that you only see a page of pending stuff until you scroll...
data.history.forEach(function (item) {
if (item.status === 'pending') {
pending.count++;
} else {
result = result.concat(item);
}
});
// Only auto-refresh if the user hasn't scrolled
// and there is more data to grab
if (transactionHistory.atYBeginning && data.history.length) {
refreshTimer.start();
}
return result;
}
}
Item {
anchors.top: recentActivityText.bottom;
@ -312,8 +253,8 @@ Item {
anchors.left: parent.left;
anchors.right: parent.right;
Item {
visible: transactionHistoryModel.count === 0 && root.initialHistoryReceived;
Item { // On empty history. We don't want to flash and then replace, so don't show until we know we're zero.
visible: transactionHistoryModel.count === 0 && transactionHistoryModel.currentPageToRetrieve < 0;
anchors.centerIn: parent;
width: parent.width - 12;
height: parent.height;
@ -385,10 +326,10 @@ Item {
model: transactionHistoryModel;
delegate: Item {
width: parent.width;
height: (model.transaction_type === "pendingCount" && root.pendingCount !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0);
height: (model.transaction_type === "pendingCount" && model.count !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0);
Item {
visible: model.transaction_type === "pendingCount" && root.pendingCount !== 0;
visible: model.transaction_type === "pendingCount" && model.count !== 0;
anchors.top: parent.top;
anchors.left: parent.left;
width: parent.width;
@ -397,7 +338,7 @@ Item {
AnonymousProRegular {
id: pendingCountText;
anchors.fill: parent;
text: root.pendingCount + ' Transaction' + (root.pendingCount > 1 ? 's' : '') + ' Pending';
text: model.count + ' Transaction' + (model.count > 1 ? 's' : '') + ' Pending';
size: 18;
color: hifi.colors.blueAccent;
verticalAlignment: Text.AlignVCenter;
@ -460,14 +401,9 @@ Item {
}
}
onAtYEndChanged: {
if (transactionHistory.atYEnd) {
if (transactionHistory.atYEnd && !transactionHistory.atYBeginning) {
console.log("User scrolled to the bottom of 'Recent Activity'.");
if (!root.historyRequestPending && !root.noMoreHistoryData) {
// Grab next page of results and append to model
root.historyRequestPending = true;
Commerce.history(++root.currentHistoryPage);
console.log("Fetching Page " + root.currentHistoryPage + " of Recent Activity...");
}
transactionHistoryModel.getNextPage();
}
}
}
@ -506,40 +442,6 @@ Item {
return year + '-' + month + '-' + day + '<br>' + drawnHour + ':' + min + amOrPm;
}
function calculatePendingAndInvalidated(startingPendingCount) {
var pendingCount = startingPendingCount ? startingPendingCount : 0;
for (var i = 0; i < transactionHistoryModel.count; i++) {
if (transactionHistoryModel.get(i).status === "pending") {
pendingCount++;
}
}
root.pendingCount = pendingCount;
if (pendingCount > 0) {
transactionHistoryModel.insert(0, {"transaction_type": "pendingCount"});
}
}
//
// Function Name: fromScript()
//
// Relevant Variables:
// None
//
// Arguments:
// message: The message sent from the JavaScript.
// Messages are in format "{method, params}", like json-rpc.
//
// Description:
// Called when a message is received from a script.
//
function fromScript(message) {
switch (message.method) {
default:
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
}
}
signal sendSignalToWallet(var msg);
//

View file

@ -0,0 +1,168 @@
//
// PSFListModel.qml
// qml/hifi/commerce/common
//
// PSFListModel
// "PSF" stands for:
// - Paged
// - Sortable
// - Filterable
//
// Created by Zach Fox on 2018-05-15
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
ListModel {
id: root;
// Used when printing debug statements
property string listModelName: endpoint;
// Parameters. Even if you override getPage, below, please set these for clarity and consistency, when applicable.
// E.g., your getPage function could refer to this sortKey, etc.
property string endpoint;
property string sortProperty; // Currently only handles sorting on one column, which fits with current needs and tables.
property bool sortAscending;
property string sortKey: !sortProperty ? '' : (sortProperty + "," + (sortAscending ? "ASC" : "DESC"));
property string searchFilter: "";
property string tagsFilter;
// QML fires the following changed handlers even when first instantiating the Item. So we need a guard against firing them too early.
property bool initialized: false;
Component.onCompleted: initialized = true;
onEndpointChanged: if (initialized) { getFirstPage('delayClear'); }
onSortKeyChanged: if (initialized) { getFirstPage('delayClear'); }
onSearchFilterChanged: if (initialized) { getFirstPage('delayClear'); }
onTagsFilterChanged: if (initialized) { getFirstPage('delayClear'); }
property int itemsPerPage: 100;
// State.
property int currentPageToRetrieve: 0; // 0 = before first page. -1 = we have them all. Otherwise 1-based page number.
property bool retrievedAtLeastOnePage: false;
// We normally clear on reset. But if we want to "refresh", we can delay clearing the model until we get a result.
// Not normally set directly, but rather by giving a truthy argument to getFirstPage(true);
property bool delayedClear: false;
function resetModel() {
if (!delayedClear) { root.clear(); }
currentPageToRetrieve = 1;
retrievedAtLeastOnePage = false;
}
// Page processing.
// Override to return one property of data, and/or to transform the elements. Must return an array of model elements.
property var processPage: function (data) { return data; }
property var listView; // Optional. For debugging.
// Check consistency and call processPage.
function handlePage(error, response) {
var processed;
console.debug('handlePage', listModelName, additionalFirstPageRequested, error, JSON.stringify(response));
function fail(message) {
console.warn("Warning page fail", listModelName, JSON.stringify(message));
currentPageToRetrieve = -1;
requestPending = false;
delayedClear = false;
}
if (error || (response.status !== 'success')) {
return fail(error || response.status);
}
if (!requestPending) {
return fail("No request in flight.");
}
requestPending = false;
if (response.current_page && response.current_page !== currentPageToRetrieve) { // Not all endpoints specify this property.
return fail("Mismatched page, expected:" + currentPageToRetrieve);
}
processed = processPage(response.data || response);
if (response.total_pages && (response.total_pages === currentPageToRetrieve)) {
currentPageToRetrieve = -1;
}
if (delayedClear) {
root.clear();
delayedClear = false;
}
root.append(processed); // FIXME keep index steady, and apply any post sort
retrievedAtLeastOnePage = true;
// Suppose two properties change at once, and both of their change handlers request a new first page.
// (An example is when the a filter box gets cleared with text in it, so that the search and tags are both reset.)
// Or suppose someone just types new search text quicker than the server response.
// In these cases, we would have multiple requests in flight, and signal based responses aren't generally very good
// at matching up the right handler with the right message. Rather than require all the APIs to carefully handle such,
// and also to cut down on useless requests, we take care of that case here.
if (additionalFirstPageRequested) {
console.debug('deferred getFirstPage', listModelName);
additionalFirstPageRequested = false;
getFirstPage('delayedClear');
}
}
function debugView(label) {
if (!listView) { return; }
console.debug(label, listModelName, 'perPage:', itemsPerPage, 'count:', listView.count,
'index:', listView.currentIndex, 'section:', listView.currentSection,
'atYBeginning:', listView.atYBeginning, 'atYEnd:', listView.atYEnd,
'y:', listView.y, 'contentY:', listView.contentY);
}
// Override either http or getPage.
property var http; // An Item that has a request function.
property var getPage: function () { // Any override MUST call handlePage(), above, even if results empty.
if (!http) { return console.warn("Neither http nor getPage was set for", listModelName); }
// If it is a path starting with slash, add the metaverseServer domain.
var url = /^\//.test(endpoint) ? (Account.metaverseServerURL + endpoint) : endpoint;
var parameters = [
'per_page=' + itemsPerPage,
'page=' + currentPageToRetrieve
];
if (searchFilter) {
parameters.splice(parameters.length, 0, 'search=' + searchFilter);
}
if (sortKey) {
parameters.splice(parameters.length, 0, 'sort=' + sortKey);
}
var parametersSeparator = /\?/.test(url) ? '&' : '?';
url = url + parametersSeparator + parameters.join('&');
console.debug('getPage', listModelName, currentPageToRetrieve);
http.request({uri: url}, handlePage);
}
// Start the show by retrieving data according to `getPage()`.
// It can be custom-defined by this item's Parent.
property var getFirstPage: function (delayClear) {
if (requestPending) {
console.debug('deferring getFirstPage', listModelName);
additionalFirstPageRequested = true;
return;
}
delayedClear = !!delayClear;
resetModel();
requestPending = true;
console.debug("getFirstPage", listModelName, currentPageToRetrieve);
getPage();
}
property bool additionalFirstPageRequested: false;
property bool requestPending: false; // For de-bouncing getNextPage.
// This function, will get the _next_ page of data according to `getPage()`.
// It can be custom-defined by this item's Parent. Typical usage:
// ListView {
// id: theList
// model: thisPSFListModelId
// onAtYEndChanged: if (theList.atYEnd && !theList.atYBeginning) { thisPSFListModelId.getNextPage(); }
// ...}
property var getNextPage: function () {
if (requestPending || currentPageToRetrieve < 0) {
return;
}
currentPageToRetrieve++;
console.debug("getNextPage", listModelName, currentPageToRetrieve);
requestPending = true;
getPage();
}
}

View file

@ -34,41 +34,16 @@ StackView {
height: parent !== null ? parent.height : undefined
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;
RootHttpRequest { id: http; }
signal sendToScript(var message);
function rpc(method, parameters, callback) {
console.debug('TabletAddressDialog: rpc: method = ', method, 'parameters = ', parameters, 'callback = ', callback)
rpcCalls[rpcCounter] = callback;
var message = {method: method, params: parameters, id: rpcCounter++, jsonrpc: "2.0"};
sendToScript(message);
}
function fromScript(message) {
if (message.method === 'refreshFeeds') {
var feeds = [happeningNow, places, snapshots];
console.debug('TabletAddressDialog::fromScript: refreshFeeds', 'feeds = ', feeds);
feeds.forEach(function(feed) {
feed.protocol = encodeURIComponent(message.protocolSignature);
Qt.callLater(feed.fillDestinations);
});
return;
switch (message.method) {
case 'http.response':
http.handleHttpResponse(message);
break;
}
var callback = rpcCalls[message.id];
if (!callback) {
// FIXME: We often recieve very long messages here, the logging of which is drastically slowing down the main thread
//console.log('No callback for message fromScript', JSON.stringify(message));
return;
}
delete rpcCalls[message.id];
callback(message.error, message.result);
}
Component { id: tabletWebView; TabletWebView {} }
@ -346,12 +321,11 @@ StackView {
width: parent.width;
cardWidth: 312 + (2 * 4);
cardHeight: 163 + (2 * 4);
metaverseServerUrl: addressBarDialog.metaverseServerUrl;
labelText: 'HAPPENING NOW';
actions: 'announcement';
filter: addressLine.text;
goFunction: goCard;
rpc: root.rpc;
http: http;
}
Feed {
id: places;
@ -359,12 +333,11 @@ StackView {
cardWidth: 210;
cardHeight: 110 + messageHeight;
messageHeight: 44;
metaverseServerUrl: addressBarDialog.metaverseServerUrl;
labelText: 'PLACES';
actions: 'concurrency';
filter: addressLine.text;
goFunction: goCard;
rpc: root.rpc;
http: http;
}
Feed {
id: snapshots;
@ -373,12 +346,11 @@ StackView {
cardHeight: 75 + messageHeight + 4;
messageHeight: 32;
textPadding: 6;
metaverseServerUrl: addressBarDialog.metaverseServerUrl;
labelText: 'RECENT SNAPS';
actions: 'snapshot';
filter: addressLine.text;
goFunction: goCard;
rpc: root.rpc;
http: http;
}
}
}

View file

@ -65,6 +65,18 @@ Item {
return false;
}
function closeDialog() {
if (openMessage != null) {
openMessage.destroy();
openMessage = null;
}
if (openModal != null) {
openModal.destroy();
openModal = null;
}
}
function isUrlLoaded(url) {
if (currentApp >= 0) {
var currentAppUrl = tabletApps.get(currentApp).appUrl;

View file

@ -697,8 +697,8 @@ private:
};
/**jsdoc
* <p>The <code>Controller.Hardware.Application</code> object has properties representing Interface's state. The property
* values are integer IDs, uniquely identifying each output. <em>Read-only.</em> These can be mapped to actions or functions or
* <p>The <code>Controller.Hardware.Application</code> object has properties representing Interface's state. The property
* values are integer IDs, uniquely identifying each output. <em>Read-only.</em> These can be mapped to actions or functions or
* <code>Controller.Standard</code> items in a {@link RouteObject} mapping (e.g., using the {@link RouteObject#when} method).
* Each data value is either <code>1.0</code> for "true" or <code>0.0</code> for "false".</p>
* <table>
@ -780,7 +780,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET);
// Ignore any previous crashes if running from command line with a test script.
// Ignore any previous crashes if running from command line with a test script.
bool inTestMode { false };
for (int i = 0; i < argc; ++i) {
QString parameter(argv[i]);
@ -1108,7 +1108,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
qCDebug(interfaceapp) << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
qCDebug(interfaceapp) << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION;
qCDebug(interfaceapp) << "[VERSION] VERSION:" << BuildInfo::VERSION;
qCDebug(interfaceapp) << "[VERSION] BUILD_BRANCH:" << BuildInfo::BUILD_BRANCH;
qCDebug(interfaceapp) << "[VERSION] BUILD_TYPE_STRING:" << BuildInfo::BUILD_TYPE_STRING;
qCDebug(interfaceapp) << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES;
#if USE_STABLE_GLOBAL_SERVICES
qCDebug(interfaceapp) << "[VERSION] We will use STABLE global services.";
@ -1365,11 +1365,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
initializeGL();
qCDebug(interfaceapp, "Initialized GL");
// Initialize the display plugin architecture
// Initialize the display plugin architecture
initializeDisplayPlugins();
qCDebug(interfaceapp, "Initialized Display");
// Create the rendering engine. This can be slow on some machines due to lots of
// Create the rendering engine. This can be slow on some machines due to lots of
// GPU pipeline creation.
initializeRenderEngine();
qCDebug(interfaceapp, "Initialized Render Engine.");
@ -1413,7 +1413,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// In practice we shouldn't run across installs that don't have a known installer type.
// Client or Client+Server installs should always have the installer.ini next to their
// respective interface.exe, and Steam installs will be detected as such. If a user were
// to delete the installer.ini, though, and as an example, we won't know the context of the
// to delete the installer.ini, though, and as an example, we won't know the context of the
// original install.
constexpr auto INSTALLER_KEY_TYPE = "type";
constexpr auto INSTALLER_KEY_CAMPAIGN = "campaign";
@ -1461,6 +1461,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
{ "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) },
{ "installer_campaign", installerCampaign },
{ "installer_type", installerType },
{ "build_type", BuildInfo::BUILD_TYPE_STRING },
{ "previousSessionCrashed", _previousSessionCrashed },
{ "previousSessionRuntime", sessionRunTime.get() },
{ "cpu_architecture", QSysInfo::currentCpuArchitecture() },
@ -2178,7 +2179,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
if (testProperty.isValid()) {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
const auto testScript = property(hifi::properties::TEST).toUrl();
// Set last parameter to exit interface when the test script finishes, if so requested
scriptEngines->loadScript(testScript, false, false, false, false, quitWhenFinished);
@ -2395,7 +2396,7 @@ void Application::onAboutToQuit() {
}
}
// The active display plugin needs to be loaded before the menu system is active,
// The active display plugin needs to be loaded before the menu system is active,
// so its persisted explicitly here
Setting::Handle<QString>{ ACTIVE_DISPLAY_PLUGIN_SETTING_NAME }.set(getActiveDisplayPlugin()->getName());
@ -2630,7 +2631,7 @@ void Application::initializeGL() {
// Create the GPU backend
// Requires the window context, because that's what's used in the actual rendering
// and the GPU backend will make things like the VAO which cannot be shared across
// and the GPU backend will make things like the VAO which cannot be shared across
// contexts
_glWidget->makeCurrent();
gpu::Context::init<gpu::gl::GLBackend>();
@ -2653,7 +2654,7 @@ void Application::initializeDisplayPlugins() {
auto lastActiveDisplayPluginName = activeDisplayPluginSetting.get();
auto defaultDisplayPlugin = displayPlugins.at(0);
// Once time initialization code
// Once time initialization code
DisplayPluginPointer targetDisplayPlugin;
foreach(auto displayPlugin, displayPlugins) {
displayPlugin->setContext(_gpuContext);
@ -2666,7 +2667,7 @@ void Application::initializeDisplayPlugins() {
}
// The default display plugin needs to be activated first, otherwise the display plugin thread
// may be launched by an external plugin, which is bad
// may be launched by an external plugin, which is bad
setDisplayPlugin(defaultDisplayPlugin);
// Now set the desired plugin if it's not the same as the default plugin
@ -4191,7 +4192,7 @@ bool Application::acceptSnapshot(const QString& urlString) {
static uint32_t _renderedFrameIndex { INVALID_FRAME };
bool Application::shouldPaint() const {
if (_aboutToQuit) {
if (_aboutToQuit || _window->isMinimized()) {
return false;
}
@ -4751,12 +4752,15 @@ void Application::loadSettings() {
// DONT CHECK IN
//DependencyManager::get<LODManager>()->setAutomaticLODAdjust(false);
Menu::getInstance()->loadSettings();
auto menu = Menu::getInstance();
menu->loadSettings();
// override the menu option show overlays to always be true on startup
menu->setIsOptionChecked(MenuOption::Overlays, true);
// If there is a preferred plugin, we probably messed it up with the menu settings, so fix it.
auto pluginManager = PluginManager::getInstance();
auto plugins = pluginManager->getPreferredDisplayPlugins();
auto menu = Menu::getInstance();
if (plugins.size() > 0) {
for (auto plugin : plugins) {
if (auto action = menu->getActionForOption(plugin->getName())) {
@ -5784,7 +5788,7 @@ void Application::update(float deltaTime) {
viewIsDifferentEnough = true;
}
// if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it
static const std::chrono::seconds MIN_PERIOD_BETWEEN_QUERIES { 3 };
auto now = SteadyClock::now();
@ -6152,7 +6156,9 @@ void Application::updateWindowTitle() const {
auto nodeList = DependencyManager::get<NodeList>();
auto accountManager = DependencyManager::get<AccountManager>();
QString buildVersion = " (build " + applicationVersion() + ")";
QString buildVersion = " - "
+ (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build"))
+ " " + applicationVersion();
QString loginStatus = accountManager->isLoggedIn() ? "" : " (NOT LOGGED IN)";
@ -7713,7 +7719,7 @@ void Application::sendLambdaEvent(const std::function<void()>& f) {
} else {
LambdaEvent event(f);
QCoreApplication::sendEvent(this, &event);
}
}
}
void Application::initPlugins(const QStringList& arguments) {
@ -7936,7 +7942,7 @@ void Application::setDisplayPlugin(DisplayPluginPointer newDisplayPlugin) {
}
// FIXME don't have the application directly set the state of the UI,
// instead emit a signal that the display plugin is changing and let
// instead emit a signal that the display plugin is changing and let
// the desktop lock itself. Reduces coupling between the UI and display
// plugins
auto offscreenUi = DependencyManager::get<OffscreenUi>();

View file

@ -30,9 +30,6 @@ void Application::editRenderArgs(RenderArgsEditor editor) {
void Application::paintGL() {
// Some plugins process message events, allowing paintGL to be called reentrantly.
if (_aboutToQuit || _window->isMinimized()) {
return;
}
_renderFrameCount++;
_lastTimeRendered.start();

View file

@ -18,6 +18,7 @@
#if HAS_CRASHPAD
#include <mutex>
#include <string>
#include <QStandardPaths>
#include <QDir>
@ -69,6 +70,8 @@ bool startCrashHandler() {
annotations["token"] = BACKTRACE_TOKEN;
annotations["format"] = "minidump";
annotations["version"] = BuildInfo::VERSION.toStdString();
annotations["build_number"] = BuildInfo::BUILD_NUMBER.toStdString();
annotations["build_type"] = BuildInfo::BUILD_TYPE_STRING.toStdString();
arguments.push_back("--no-rate-limit");

View file

@ -588,6 +588,10 @@ Menu::Menu() {
});
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ToggleHipsFollowing, 0, false,
avatar.get(), SLOT(setToggleHips(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawBaseOfSupport, 0, false,
avatar.get(), SLOT(setEnableDebugDrawBaseOfSupport(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false,
avatar.get(), SLOT(setEnableDebugDrawDefaultPose(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawAnimPose, 0, false,

View file

@ -30,6 +30,7 @@ namespace MenuOption {
const QString AddressBar = "Show Address Bar";
const QString Animations = "Animations...";
const QString AnimDebugDrawAnimPose = "Debug Draw Animation";
const QString AnimDebugDrawBaseOfSupport = "Debug Draw Base of Support";
const QString AnimDebugDrawDefaultPose = "Debug Draw Default Pose";
const QString AnimDebugDrawPosition= "Debug Draw Position";
const QString AskToResetSettings = "Ask To Reset Settings on Start";
@ -202,6 +203,7 @@ namespace MenuOption {
const QString ThirdPerson = "Third Person";
const QString ThreePointCalibration = "3 Point Calibration";
const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp
const QString ToggleHipsFollowing = "Toggle Hips Following";
const QString ToolWindow = "Tool Window";
const QString TransmitterDrive = "Transmitter Drive";
const QString TurnWithHead = "Turn using Head";

View file

@ -468,13 +468,14 @@ void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) {
_shouldRender = shouldRenderAvatars;
const render::ScenePointer& scene = qApp->getMain3DScene();
render::Transaction transaction;
auto avatarHashCopy = getHashCopy();
if (_shouldRender) {
for (auto avatarData : _avatarHash) {
for (auto avatarData : avatarHashCopy) {
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
avatar->addToScene(avatar, scene, transaction);
}
} else {
for (auto avatarData : _avatarHash) {
for (auto avatarData : avatarHashCopy) {
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
avatar->removeFromScene(avatar, scene, transaction);
}
@ -514,7 +515,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
glm::vec3 normDirection = glm::normalize(ray.direction);
for (auto avatarData : _avatarHash) {
auto avatarHashCopy = getHashCopy();
for (auto avatarData : avatarHashCopy) {
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) ||
(avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) {

View file

@ -52,6 +52,7 @@
#include "MyHead.h"
#include "MySkeletonModel.h"
#include "AnimUtil.h"
#include "Application.h"
#include "AvatarManager.h"
#include "AvatarActionHold.h"
@ -422,12 +423,12 @@ void MyAvatar::update(float deltaTime) {
}
#ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE
glm::vec3 p = transformPoint(getSensorToWorldMatrix(), getControllerPoseInAvatarFrame(controller::Pose::HEAD) *
glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y));
DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f));
p = transformPoint(getSensorToWorldMatrix(), getHMDSensorPosition() +
glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y));
DebugDraw::getInstance().addMarker("facing", getOrientation(), p, glm::vec4(1.0f));
auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation());
glm::vec3 worldFacingAverage = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y));
glm::vec3 worldFacing = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y));
DebugDraw::getInstance().drawRay(worldHeadPos, worldHeadPos + worldFacing, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
DebugDraw::getInstance().drawRay(worldHeadPos, worldHeadPos + worldFacingAverage, glm::vec4(0.0f, 0.0f, 1.0f, 1.0f));
#endif
if (_goToPending) {
@ -712,7 +713,8 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
_hmdSensorOrientation = glmExtractRotation(hmdSensorMatrix);
auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
if (headPose.isValid()) {
_headControllerFacing = getFacingDir2D(headPose.rotation);
glm::quat bodyOrientation = computeBodyFacingFromHead(headPose.rotation, Vectors::UNIT_Y);
_headControllerFacing = getFacingDir2D(bodyOrientation);
} else {
_headControllerFacing = glm::vec2(1.0f, 0.0f);
}
@ -1079,6 +1081,22 @@ float loadSetting(Settings& settings, const QString& name, float defaultValue) {
return value;
}
void MyAvatar::setToggleHips(bool followHead) {
_follow.setToggleHipsFollowing(followHead);
}
void MyAvatar::FollowHelper::setToggleHipsFollowing(bool followHead) {
_toggleHipsFollowing = followHead;
}
bool MyAvatar::FollowHelper::getToggleHipsFollowing() const {
return _toggleHipsFollowing;
}
void MyAvatar::setEnableDebugDrawBaseOfSupport(bool isEnabled) {
_enableDebugDrawBaseOfSupport = isEnabled;
}
void MyAvatar::setEnableDebugDrawDefaultPose(bool isEnabled) {
_enableDebugDrawDefaultPose = isEnabled;
@ -1210,6 +1228,8 @@ void MyAvatar::loadData() {
settings.endGroup();
setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible));
_follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing));
setEnableDebugDrawBaseOfSupport(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawBaseOfSupport));
setEnableDebugDrawDefaultPose(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawDefaultPose));
setEnableDebugDrawAnimPose(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawAnimPose));
setEnableDebugDrawPosition(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawPosition));
@ -2819,6 +2839,7 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
if (headPose.isValid()) {
headPosition = headPose.translation;
// AJT: TODO: can remove this Y_180
headOrientation = headPose.rotation * Quaternions::Y_180;
}
const glm::quat headOrientationYawOnly = cancelOutRollAndPitch(headOrientation);
@ -2841,6 +2862,8 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
// eyeToNeck offset is relative full HMD orientation.
// while neckToRoot offset is only relative to HMDs yaw.
// Y_180 is necessary because rig is z forward and hmdOrientation is -z forward
// AJT: TODO: can remove this Y_180, if we remove the higher level one.
glm::vec3 headToNeck = headOrientation * Quaternions::Y_180 * (localNeck - localHead);
glm::vec3 neckToRoot = headOrientationYawOnly * Quaternions::Y_180 * -localNeck;
@ -2850,6 +2873,202 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
return createMatFromQuatAndPos(headOrientationYawOnly, bodyPos);
}
// ease in function for dampening cg movement
static float slope(float num) {
const float CURVE_CONSTANT = 1.0f;
float ret = 1.0f;
if (num > 0.0f) {
ret = 1.0f - (1.0f / (1.0f + CURVE_CONSTANT * num));
}
return ret;
}
// This function gives a soft clamp at the edge of the base of support
// dampenCgMovement returns the damped cg value in Avatar space.
// cgUnderHeadHandsAvatarSpace is also in Avatar space
// baseOfSupportScale is based on the height of the user
static glm::vec3 dampenCgMovement(glm::vec3 cgUnderHeadHandsAvatarSpace, float baseOfSupportScale) {
float distanceFromCenterZ = cgUnderHeadHandsAvatarSpace.z;
float distanceFromCenterX = cgUnderHeadHandsAvatarSpace.x;
// In the forward direction we need a different scale because forward is in
// the direction of the hip extensor joint, which means bending usually happens
// well before reaching the edge of the base of support.
const float clampFront = DEFAULT_AVATAR_SUPPORT_BASE_FRONT * DEFAULT_AVATAR_FORWARD_DAMPENING_FACTOR * baseOfSupportScale;
float clampBack = DEFAULT_AVATAR_SUPPORT_BASE_BACK * DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR * baseOfSupportScale;
float clampLeft = DEFAULT_AVATAR_SUPPORT_BASE_LEFT * DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR * baseOfSupportScale;
float clampRight = DEFAULT_AVATAR_SUPPORT_BASE_RIGHT * DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR * baseOfSupportScale;
glm::vec3 dampedCg(0.0f, 0.0f, 0.0f);
// find the damped z coord of the cg
if (cgUnderHeadHandsAvatarSpace.z < 0.0f) {
// forward displacement
dampedCg.z = slope(fabs(distanceFromCenterZ / clampFront)) * clampFront;
} else {
// backwards displacement
dampedCg.z = slope(fabs(distanceFromCenterZ / clampBack)) * clampBack;
}
// find the damped x coord of the cg
if (cgUnderHeadHandsAvatarSpace.x > 0.0f) {
// right of center
dampedCg.x = slope(fabs(distanceFromCenterX / clampRight)) * clampRight;
} else {
// left of center
dampedCg.x = slope(fabs(distanceFromCenterX / clampLeft)) * clampLeft;
}
return dampedCg;
}
// computeCounterBalance returns the center of gravity in Avatar space
glm::vec3 MyAvatar::computeCounterBalance() const {
struct JointMass {
QString name;
float weight;
glm::vec3 position;
JointMass() {};
JointMass(QString n, float w, glm::vec3 p) {
name = n;
weight = w;
position = p;
}
};
// init the body part weights
JointMass cgHeadMass(QString("Head"), DEFAULT_AVATAR_HEAD_MASS, glm::vec3(0.0f, 0.0f, 0.0f));
JointMass cgLeftHandMass(QString("LeftHand"), DEFAULT_AVATAR_LEFTHAND_MASS, glm::vec3(0.0f, 0.0f, 0.0f));
JointMass cgRightHandMass(QString("RightHand"), DEFAULT_AVATAR_RIGHTHAND_MASS, glm::vec3(0.0f, 0.0f, 0.0f));
glm::vec3 tposeHead = DEFAULT_AVATAR_HEAD_POS;
glm::vec3 tposeHips = glm::vec3(0.0f, 0.0f, 0.0f);
if (_skeletonModel->getRig().indexOfJoint(cgHeadMass.name) != -1) {
cgHeadMass.position = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint(cgHeadMass.name));
tposeHead = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint(cgHeadMass.name));
}
if (_skeletonModel->getRig().indexOfJoint(cgLeftHandMass.name) != -1) {
cgLeftHandMass.position = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint(cgLeftHandMass.name));
} else {
cgLeftHandMass.position = DEFAULT_AVATAR_LEFTHAND_POS;
}
if (_skeletonModel->getRig().indexOfJoint(cgRightHandMass.name) != -1) {
cgRightHandMass.position = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint(cgRightHandMass.name));
} else {
cgRightHandMass.position = DEFAULT_AVATAR_RIGHTHAND_POS;
}
if (_skeletonModel->getRig().indexOfJoint("Hips") != -1) {
tposeHips = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("Hips"));
}
// find the current center of gravity position based on head and hand moments
glm::vec3 sumOfMoments = (cgHeadMass.weight * cgHeadMass.position) + (cgLeftHandMass.weight * cgLeftHandMass.position) + (cgRightHandMass.weight * cgRightHandMass.position);
float totalMass = cgHeadMass.weight + cgLeftHandMass.weight + cgRightHandMass.weight;
glm::vec3 currentCg = (1.0f / totalMass) * sumOfMoments;
currentCg.y = 0.0f;
// dampening the center of gravity, in effect, limits the value to the perimeter of the base of support
float baseScale = 1.0f;
if (getUserEyeHeight() > 0.0f) {
baseScale = getUserEyeHeight() / DEFAULT_AVATAR_EYE_HEIGHT;
}
glm::vec3 desiredCg = dampenCgMovement(currentCg, baseScale);
// compute hips position to maintain desiredCg
glm::vec3 counterBalancedForHead = (totalMass + DEFAULT_AVATAR_HIPS_MASS) * desiredCg;
counterBalancedForHead -= sumOfMoments;
glm::vec3 counterBalancedCg = (1.0f / DEFAULT_AVATAR_HIPS_MASS) * counterBalancedForHead;
// find the height of the hips
glm::vec3 xzDiff((cgHeadMass.position.x - counterBalancedCg.x), 0.0f, (cgHeadMass.position.z - counterBalancedCg.z));
float headMinusHipXz = glm::length(xzDiff);
float headHipDefault = glm::length(tposeHead - tposeHips);
float hipHeight = 0.0f;
if (headHipDefault > headMinusHipXz) {
hipHeight = sqrtf((headHipDefault * headHipDefault) - (headMinusHipXz * headMinusHipXz));
}
counterBalancedCg.y = (cgHeadMass.position.y - hipHeight);
// this is to be sure that the feet don't lift off the floor.
// add 5 centimeters to allow for going up on the toes.
if (counterBalancedCg.y > (tposeHips.y + 0.05f)) {
// if the height is higher than default hips, clamp to default hips
counterBalancedCg.y = tposeHips.y + 0.05f;
}
return counterBalancedCg;
}
// this function matches the hips rotation to the new cghips-head axis
// headOrientation, headPosition and hipsPosition are in avatar space
// returns the matrix of the hips in Avatar space
static glm::mat4 computeNewHipsMatrix(glm::quat headOrientation, glm::vec3 headPosition, glm::vec3 hipsPosition) {
glm::quat bodyOrientation = computeBodyFacingFromHead(headOrientation, Vectors::UNIT_Y);
const float MIX_RATIO = 0.3f;
glm::quat hipsRot = safeLerp(Quaternions::IDENTITY, bodyOrientation, MIX_RATIO);
glm::vec3 hipsFacing = hipsRot * Vectors::UNIT_Z;
glm::vec3 spineVec = headPosition - hipsPosition;
glm::vec3 u, v, w;
generateBasisVectors(glm::normalize(spineVec), hipsFacing, u, v, w);
return glm::mat4(glm::vec4(w, 0.0f),
glm::vec4(u, 0.0f),
glm::vec4(v, 0.0f),
glm::vec4(hipsPosition, 1.0f));
}
static void drawBaseOfSupport(float baseOfSupportScale, float footLocal, glm::mat4 avatarToWorld) {
// scale the base of support based on user height
float clampFront = DEFAULT_AVATAR_SUPPORT_BASE_FRONT * baseOfSupportScale;
float clampBack = DEFAULT_AVATAR_SUPPORT_BASE_BACK * baseOfSupportScale;
float clampLeft = DEFAULT_AVATAR_SUPPORT_BASE_LEFT * baseOfSupportScale;
float clampRight = DEFAULT_AVATAR_SUPPORT_BASE_RIGHT * baseOfSupportScale;
float floor = footLocal + 0.05f;
// transform the base of support corners to world space
glm::vec3 frontRight = transformPoint(avatarToWorld, { clampRight, floor, clampFront });
glm::vec3 frontLeft = transformPoint(avatarToWorld, { clampLeft, floor, clampFront });
glm::vec3 backRight = transformPoint(avatarToWorld, { clampRight, floor, clampBack });
glm::vec3 backLeft = transformPoint(avatarToWorld, { clampLeft, floor, clampBack });
// draw the borders
const glm::vec4 rayColor = { 1.0f, 0.0f, 0.0f, 1.0f };
DebugDraw::getInstance().drawRay(backLeft, frontLeft, rayColor);
DebugDraw::getInstance().drawRay(backLeft, backRight, rayColor);
DebugDraw::getInstance().drawRay(backRight, frontRight, rayColor);
DebugDraw::getInstance().drawRay(frontLeft, frontRight, rayColor);
}
// this function finds the hips position using a center of gravity model that
// balances the head and hands with the hips over the base of support
// returns the rotation (-z forward) and position of the Avatar in Sensor space
glm::mat4 MyAvatar::deriveBodyUsingCgModel() const {
glm::mat4 sensorToWorldMat = getSensorToWorldMatrix();
glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat);
auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
glm::mat4 sensorHeadMat = createMatFromQuatAndPos(headPose.rotation * Quaternions::Y_180, headPose.translation);
// convert into avatar space
glm::mat4 avatarToWorldMat = getTransform().getMatrix();
glm::mat4 avatarHeadMat = glm::inverse(avatarToWorldMat) * sensorToWorldMat * sensorHeadMat;
if (_enableDebugDrawBaseOfSupport) {
float scaleBaseOfSupport = getUserEyeHeight() / DEFAULT_AVATAR_EYE_HEIGHT;
glm::vec3 rightFootPositionLocal = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("RightFoot"));
drawBaseOfSupport(scaleBaseOfSupport, rightFootPositionLocal.y, avatarToWorldMat);
}
// get the new center of gravity
const glm::vec3 cgHipsPosition = computeCounterBalance();
// find the new hips rotation using the new head-hips axis as the up axis
glm::mat4 avatarHipsMat = computeNewHipsMatrix(glmExtractRotation(avatarHeadMat), extractTranslation(avatarHeadMat), cgHipsPosition);
// convert hips from avatar to sensor space
// The Y_180 is to convert from z forward to -z forward.
return worldToSensorMat * avatarToWorldMat * avatarHipsMat;
}
float MyAvatar::getUserHeight() const {
return _userHeight.get();
}
@ -3014,9 +3233,7 @@ void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) {
bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees
glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix);
return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD;
}
bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
@ -3087,11 +3304,19 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
AnimPose followWorldPose(currentWorldMatrix);
glm::quat currentHipsLocal = myAvatar.getAbsoluteJointRotationInObjectFrame(myAvatar.getJointIndex("Hips"));
const glm::quat hipsinWorldSpace = followWorldPose.rot() * (Quaternions::Y_180 * (currentHipsLocal));
const glm::vec3 avatarUpWorld = glm::normalize(followWorldPose.rot()*(Vectors::UP));
glm::quat resultingSwingInWorld;
glm::quat resultingTwistInWorld;
swingTwistDecomposition(hipsinWorldSpace, avatarUpWorld, resultingSwingInWorld, resultingTwistInWorld);
// remove scale present from sensorToWorldMatrix
followWorldPose.scale() = glm::vec3(1.0f);
if (isActive(Rotation)) {
followWorldPose.rot() = glmExtractRotation(desiredWorldMatrix);
//use the hmd reading for the hips follow
followWorldPose.rot() = glmExtractRotation(desiredWorldMatrix);
}
if (isActive(Horizontal)) {
glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix);
@ -3487,6 +3712,10 @@ void MyAvatar::updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose&
}
}
bool MyAvatar::isRecenteringHorizontally() const {
return _follow.isActive(FollowHelper::Horizontal);
}
const MyHead* MyAvatar::getMyHead() const {
return static_cast<const MyHead*>(getHead());
}

View file

@ -105,6 +105,9 @@ class MyAvatar : public Avatar {
* by 30cm. <em>Read-only.</em>
* @property {Pose} rightHandTipPose - The pose of the right hand as determined by the hand controllers, with the position
* by 30cm. <em>Read-only.</em>
* @property {boolean} centerOfGravityModelEnabled=true - If <code>true</code> then the avatar hips are placed according to the center of
* gravity model that balance the center of gravity over the base of support of the feet. Setting the value <code>false</code>
* will result in the default behaviour where the hips are placed under the head.
* @property {boolean} hmdLeanRecenterEnabled=true - If <code>true</code> then the avatar is re-centered to be under the
* head's position. In room-scale VR, this behavior is what causes your avatar to follow your HMD as you walk around
* the room. Setting the value <code>false</code> is useful if you want to pin the avatar to a fixed position.
@ -199,6 +202,7 @@ class MyAvatar : public Avatar {
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
Q_PROPERTY(bool isAway READ getIsAway WRITE setAway)
Q_PROPERTY(bool centerOfGravityModelEnabled READ getCenterOfGravityModelEnabled WRITE setCenterOfGravityModelEnabled)
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
Q_PROPERTY(bool collisionsEnabled READ getCollisionsEnabled WRITE setCollisionsEnabled)
Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled)
@ -480,7 +484,16 @@ public:
*/
Q_INVOKABLE QString getDominantHand() const { return _dominantHand; }
/**jsdoc
* @function MyAvatar.setCenterOfGravityModelEnabled
* @param {boolean} enabled
*/
Q_INVOKABLE void setCenterOfGravityModelEnabled(bool value) { _centerOfGravityModelEnabled = value; }
/**jsdoc
* @function MyAvatar.getCenterOfGravityModelEnabled
* @returns {boolean}
*/
Q_INVOKABLE bool getCenterOfGravityModelEnabled() const { return _centerOfGravityModelEnabled; }
/**jsdoc
* @function MyAvatar.setHMDLeanRecenterEnabled
* @param {boolean} enabled
@ -564,6 +577,13 @@ public:
*/
Q_INVOKABLE void triggerRotationRecenter();
/**jsdoc
*The isRecenteringHorizontally function returns true if MyAvatar
*is translating the root of the Avatar to keep the center of gravity under the head.
*isActive(Horizontal) is returned.
*@function MyAvatar.isRecenteringHorizontally
*/
Q_INVOKABLE bool isRecenteringHorizontally() const;
eyeContactTarget getEyeContactTarget();
@ -956,10 +976,18 @@ public:
void removeHoldAction(AvatarActionHold* holdAction); // thread-safe
void updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& postUpdatePose);
// derive avatar body position and orientation from the current HMD Sensor location.
// results are in HMD frame
// results are in sensor frame (-z forward)
glm::mat4 deriveBodyFromHMDSensor() const;
glm::vec3 computeCounterBalance() const;
// derive avatar body position and orientation from using the current HMD Sensor location in relation to the previous
// location of the base of support of the avatar.
// results are in sensor frame (-z foward)
glm::mat4 deriveBodyUsingCgModel() const;
/**jsdoc
* @function MyAvatar.isUp
* @param {Vec3} direction
@ -1107,7 +1135,16 @@ public slots:
*/
Q_INVOKABLE void updateMotionBehaviorFromMenu();
/**jsdoc
* @function MyAvatar.setToggleHips
* @param {boolean} enabled
*/
void setToggleHips(bool followHead);
/**jsdoc
* @function MyAvatar.setEnableDebugDrawBaseOfSupport
* @param {boolean} enabled
*/
void setEnableDebugDrawBaseOfSupport(bool isEnabled);
/**jsdoc
* @function MyAvatar.setEnableDebugDrawDefaultPose
* @param {boolean} enabled
@ -1458,8 +1495,8 @@ private:
glm::quat _hmdSensorOrientation;
glm::vec3 _hmdSensorPosition;
// cache head controller pose in sensor space
glm::vec2 _headControllerFacing; // facing vector in xz plane
glm::vec2 _headControllerFacingMovingAverage { 0, 0 }; // facing vector in xz plane
glm::vec2 _headControllerFacing; // facing vector in xz plane (sensor space)
glm::vec2 _headControllerFacingMovingAverage { 0.0f, 0.0f }; // facing vector in xz plane (sensor space)
// cache of the current body position and orientation of the avatar's body,
// in sensor space.
@ -1495,9 +1532,12 @@ private:
void setForceActivateVertical(bool val);
bool getForceActivateHorizontal() const;
void setForceActivateHorizontal(bool val);
std::atomic<bool> _forceActivateRotation{ false };
std::atomic<bool> _forceActivateVertical{ false };
std::atomic<bool> _forceActivateHorizontal{ false };
bool getToggleHipsFollowing() const;
void setToggleHipsFollowing(bool followHead);
std::atomic<bool> _forceActivateRotation { false };
std::atomic<bool> _forceActivateVertical { false };
std::atomic<bool> _forceActivateHorizontal { false };
std::atomic<bool> _toggleHipsFollowing { true };
};
FollowHelper _follow;
@ -1510,6 +1550,7 @@ private:
bool _prevShouldDrawHead;
bool _rigEnabled { true };
bool _enableDebugDrawBaseOfSupport { false };
bool _enableDebugDrawDefaultPose { false };
bool _enableDebugDrawAnimPose { false };
bool _enableDebugDrawHandControllers { false };
@ -1532,6 +1573,7 @@ private:
std::map<controller::Action, controller::Pose> _controllerPoseMap;
mutable std::mutex _controllerPoseMapMutex;
bool _centerOfGravityModelEnabled { true };
bool _hmdLeanRecenterEnabled { true };
bool _sprint { false };

View file

@ -45,7 +45,14 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
return result;
}
glm::mat4 hipsMat = myAvatar->deriveBodyFromHMDSensor();
glm::mat4 hipsMat;
if (myAvatar->getCenterOfGravityModelEnabled()) {
// then we use center of gravity model
hipsMat = myAvatar->deriveBodyUsingCgModel();
} else {
// otherwise use the default of putting the hips under the head
hipsMat = myAvatar->deriveBodyFromHMDSensor();
}
glm::vec3 hipsPos = extractTranslation(hipsMat);
glm::quat hipsRot = glmExtractRotation(hipsMat);
@ -53,8 +60,11 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
glm::mat4 avatarToSensorMat = worldToSensorMat * avatarToWorldMat;
// dampen hips rotation, by mixing it with the avatar orientation in sensor space
const float MIX_RATIO = 0.5f;
hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO);
// turning this off for center of gravity model because it is already mixed in there
if (!(myAvatar->getCenterOfGravityModelEnabled())) {
const float MIX_RATIO = 0.5f;
hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO);
}
if (isFlying) {
// rotate the hips back to match the flying animation.
@ -73,6 +83,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
hipsPos = headPos + tiltRot * (hipsPos - headPos);
}
// AJT: TODO can we remove this?
return AnimPose(hipsRot * Quaternions::Y_180, hipsPos);
}
@ -170,6 +181,15 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
}
}
bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS);
if (isFlying != _prevIsFlying) {
const float FLY_TO_IDLE_HIPS_TRANSITION_TIME = 0.5f;
_flyIdleTimer = FLY_TO_IDLE_HIPS_TRANSITION_TIME;
} else {
_flyIdleTimer -= deltaTime;
}
_prevIsFlying = isFlying;
// if hips are not under direct control, estimate the hips position.
if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) {
bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS);
@ -181,14 +201,28 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying);
// timescale in seconds
const float TRANS_HORIZ_TIMESCALE = 0.15f;
const float TRANS_VERT_TIMESCALE = 0.01f; // We want the vertical component of the hips to follow quickly to prevent spine squash/stretch.
const float ROT_TIMESCALE = 0.15f;
const float FLY_IDLE_TRANSITION_TIMESCALE = 0.25f;
float transHorizAlpha, transVertAlpha, rotAlpha;
if (_flyIdleTimer < 0.0f) {
transHorizAlpha = glm::min(deltaTime / TRANS_HORIZ_TIMESCALE, 1.0f);
transVertAlpha = glm::min(deltaTime / TRANS_VERT_TIMESCALE, 1.0f);
rotAlpha = glm::min(deltaTime / ROT_TIMESCALE, 1.0f);
} else {
transHorizAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f);
transVertAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f);
rotAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f);
}
// smootly lerp hips, in sensorframe, with different coeff for horiz and vertical translation.
const float ROT_ALPHA = 0.9f;
const float TRANS_HORIZ_ALPHA = 0.9f;
const float TRANS_VERT_ALPHA = 0.1f;
float hipsY = hips.trans().y;
hips.trans() = lerp(hips.trans(), _prevHips.trans(), TRANS_HORIZ_ALPHA);
hips.trans().y = lerp(hipsY, _prevHips.trans().y, TRANS_VERT_ALPHA);
hips.rot() = safeLerp(hips.rot(), _prevHips.rot(), ROT_ALPHA);
hips.trans() = lerp(_prevHips.trans(), hips.trans(), transHorizAlpha);
hips.trans().y = lerp(_prevHips.trans().y, hipsY, transVertAlpha);
hips.rot() = safeLerp(_prevHips.rot(), hips.rot(), rotAlpha);
_prevHips = hips;
_prevHipsValid = true;

View file

@ -28,6 +28,8 @@ private:
AnimPose _prevHips; // sensor frame
bool _prevHipsValid { false };
bool _prevIsFlying { false };
float _flyIdleTimer { 0.0f };
std::map<int, int> _jointRotationFrameOffsetMap;
};

View file

@ -134,8 +134,14 @@ void Ledger::balance(const QStringList& keys) {
keysQuery("balance", "balanceSuccess", "balanceFailure");
}
void Ledger::inventory(const QStringList& keys) {
keysQuery("inventory", "inventorySuccess", "inventoryFailure");
void Ledger::inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage) {
QJsonObject params;
params["edition_filter"] = editionFilter;
params["type_filter"] = typeFilter;
params["title_filter"] = titleFilter;
params["page"] = page;
params["per_page"] = perPage;
keysQuery("inventory", "inventorySuccess", "inventoryFailure", params);
}
QString hfcString(const QJsonValue& sentValue, const QJsonValue& receivedValue) {
@ -260,9 +266,9 @@ void Ledger::historyFailure(QNetworkReply& reply) {
failResponse("history", reply);
}
void Ledger::history(const QStringList& keys, const int& pageNumber) {
void Ledger::history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage) {
QJsonObject params;
params["per_page"] = 100;
params["per_page"] = itemsPerPage;
params["page"] = pageNumber;
keysQuery("history", "historySuccess", "historyFailure", params);
}

View file

@ -28,8 +28,8 @@ public:
void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false);
bool receiveAt(const QString& hfc_key, const QString& signing_key);
void balance(const QStringList& keys);
void inventory(const QStringList& keys);
void history(const QStringList& keys, const int& pageNumber);
void inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage);
void history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage);
void account();
void updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings = false, const bool controlledFailure = false);
void certificateInfo(const QString& certificateId);

View file

@ -105,21 +105,21 @@ void QmlCommerce::balance() {
}
}
void QmlCommerce::inventory() {
void QmlCommerce::inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage) {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
QStringList cachedPublicKeys = wallet->listPublicKeys();
if (!cachedPublicKeys.isEmpty()) {
ledger->inventory(cachedPublicKeys);
ledger->inventory(editionFilter, typeFilter, titleFilter, page, perPage);
}
}
void QmlCommerce::history(const int& pageNumber) {
void QmlCommerce::history(const int& pageNumber, const int& itemsPerPage) {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
QStringList cachedPublicKeys = wallet->listPublicKeys();
if (!cachedPublicKeys.isEmpty()) {
ledger->history(cachedPublicKeys, pageNumber);
ledger->history(cachedPublicKeys, pageNumber, itemsPerPage);
}
}

View file

@ -73,8 +73,8 @@ protected:
Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false);
Q_INVOKABLE void balance();
Q_INVOKABLE void inventory();
Q_INVOKABLE void history(const int& pageNumber);
Q_INVOKABLE void inventory(const QString& editionFilter = QString(), const QString& typeFilter = QString(), const QString& titleFilter = QString(), const int& page = 1, const int& perPage = 20);
Q_INVOKABLE void history(const int& pageNumber, const int& itemsPerPage = 100);
Q_INVOKABLE void generateKeyPair();
Q_INVOKABLE void account();

View file

@ -314,6 +314,7 @@ Wallet::Wallet() {
auto nodeList = DependencyManager::get<NodeList>();
auto ledger = DependencyManager::get<Ledger>();
auto& packetReceiver = nodeList->getPacketReceiver();
_passphrase = new QString("");
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket");
packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket");
@ -365,6 +366,10 @@ Wallet::~Wallet() {
if (_securityImage) {
delete _securityImage;
}
if (_passphrase) {
delete _passphrase;
}
}
bool Wallet::setPassphrase(const QString& passphrase) {
@ -531,7 +536,6 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() {
// be sure to add the public key so we don't do this over and over
_publicKeys.push_back(publicKey.toBase64());
DependencyManager::get<WalletScriptingInterface>()->setWalletStatus((uint)WalletStatus::WALLET_STATUS_READY);
return true;
}
}
@ -610,7 +614,11 @@ void Wallet::updateImageProvider() {
SecurityImageProvider* securityImageProvider;
// inform offscreenUI security image provider
QQmlEngine* engine = DependencyManager::get<OffscreenUi>()->getSurfaceContext()->engine();
auto offscreenUI = DependencyManager::get<OffscreenUi>();
if (!offscreenUI) {
return;
}
QQmlEngine* engine = offscreenUI->getSurfaceContext()->engine();
securityImageProvider = reinterpret_cast<SecurityImageProvider*>(engine->imageProvider(SecurityImageProvider::PROVIDER_NAME));
securityImageProvider->setSecurityImage(_securityImage);

View file

@ -78,7 +78,7 @@ private:
QByteArray _salt;
QByteArray _iv;
QByteArray _ckey;
QString* _passphrase { new QString("") };
QString* _passphrase { nullptr };
bool _isOverridingServer { false };
bool writeWallet(const QString& newPassphrase = QString(""));

View file

@ -93,10 +93,18 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) {
static const QUrl url("dialogs/TabletConnectionFailureDialog.qml");
auto hmd = DependencyManager::get<HMDScriptingInterface>();
if (visible) {
_dialogCreatedWhileShown = tablet->property("tabletShown").toBool();
tablet->initialScreen(url);
if (!hmd->getShouldShowTablet()) {
hmd->openTablet();
}
} else if (tablet->isPathLoaded(url)) {
tablet->closeDialog();
tablet->gotoHomeScreen();
if (!_dialogCreatedWhileShown) {
hmd->closeTablet();
}
_dialogCreatedWhileShown = false;
}
}
}

View file

@ -80,6 +80,7 @@ private:
QPointer<OctreeStatsDialog> _octreeStatsDialog;
QPointer<TestingDialog> _testingDialog;
QPointer<DomainConnectionDialog> _domainConnectionDialog;
bool _dialogCreatedWhileShown { false };
bool _addressBarVisible { false };
};

View file

@ -18,7 +18,6 @@
#include "InterfaceLogging.h"
OverlayConductor::OverlayConductor() {
}
OverlayConductor::~OverlayConductor() {
@ -33,8 +32,8 @@ bool OverlayConductor::headOutsideOverlay() const {
glm::vec3 uiPos = uiTransform.getTranslation();
glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface.
const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled
const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface.
const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled
if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE ||
glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) {
return true;
@ -43,10 +42,9 @@ bool OverlayConductor::headOutsideOverlay() const {
}
bool OverlayConductor::updateAvatarIsAtRest() {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s
const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s
const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms
const float AT_REST_THRESHOLD = 0.01f;
@ -69,31 +67,6 @@ bool OverlayConductor::updateAvatarIsAtRest() {
return _currentAtRest;
}
bool OverlayConductor::updateAvatarHasDriveInput() {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms
const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s
bool desiredDriving = myAvatar->hasDriveInput();
if (desiredDriving != _desiredDriving) {
// start timer
_desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS);
}
_desiredDriving = desiredDriving;
if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) {
// timer expired
// change state!
_currentDriving = _desiredDriving;
// disable timer
_desiredDrivingTimer = 0;
}
return _currentDriving;
}
void OverlayConductor::centerUI() {
// place the overlay at the current hmd position in sensor space
auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose());
@ -115,20 +88,19 @@ void OverlayConductor::update(float dt) {
_hmdMode = false;
}
bool prevDriving = _currentDriving;
bool isDriving = updateAvatarHasDriveInput();
bool drivingChanged = prevDriving != isDriving;
bool isAtRest = updateAvatarIsAtRest();
bool isMoving = !isAtRest;
bool shouldRecenter = false;
if (_flags & SuppressedByDrive) {
if (!isDriving) {
_flags &= ~SuppressedByDrive;
shouldRecenter = true;
if (_flags & SuppressedByMove) {
if (!isMoving) {
_flags &= ~SuppressedByMove;
shouldRecenter = true;
}
} else {
if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) {
_flags |= SuppressedByDrive;
if (myAvatar->getClearOverlayWhenMoving() && isMoving) {
_flags |= SuppressedByMove;
}
}
@ -143,7 +115,6 @@ void OverlayConductor::update(float dt) {
}
}
bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask));
if (targetVisible != currentVisible) {
offscreenUi->setPinned(!targetVisible);

View file

@ -23,23 +23,17 @@ public:
private:
bool headOutsideOverlay() const;
bool updateAvatarHasDriveInput();
bool updateAvatarIsAtRest();
enum SupressionFlags {
SuppressedByDrive = 0x01,
SuppressedByMove = 0x01,
SuppressedByHead = 0x02,
SuppressMask = 0x03,
};
uint8_t _flags { SuppressedByDrive };
uint8_t _flags { SuppressedByMove };
bool _hmdMode { false };
// used by updateAvatarHasDriveInput
uint64_t _desiredDrivingTimer { 0 };
bool _desiredDriving { false };
bool _currentDriving { false };
// used by updateAvatarIsAtRest
uint64_t _desiredAtRestTimer { 0 };
bool _desiredAtRest { true };

View file

@ -100,24 +100,32 @@ void ModelOverlay::update(float deltatime) {
processMaterials();
emit DependencyManager::get<scriptable::ModelProviderFactory>()->modelAddedToScene(getID(), NestableType::Overlay, _model);
}
bool metaDirty = false;
if (_visibleDirty) {
_visibleDirty = false;
// don't show overlays in mirrors or spectator-cam unless _isVisibleInSecondaryCamera is true
uint8_t modelRenderTagMask = (_isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW);
_model->setTagMask(modelRenderTagMask, scene);
_model->setVisibleInScene(getVisible(), scene);
metaDirty = true;
}
if (_drawInFrontDirty) {
_drawInFrontDirty = false;
_model->setLayeredInFront(getDrawInFront(), scene);
metaDirty = true;
}
if (_drawInHUDDirty) {
_drawInHUDDirty = false;
_model->setLayeredInHUD(getDrawHUDLayer(), scene);
metaDirty = true;
}
if (_groupCulledDirty) {
_groupCulledDirty = false;
_model->setGroupCulled(_isGroupCulled);
_model->setGroupCulled(_isGroupCulled, scene);
metaDirty = true;
}
if (metaDirty) {
transaction.updateItem<Overlay>(getRenderItemID(), [](Overlay& data) {});
}
scene->enqueueTransaction(transaction);

View file

@ -9,7 +9,9 @@
//
#include "AnimUtil.h"
#include "GLMHelpers.h"
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <DebugDraw.h>
// TODO: use restrict keyword
// TODO: excellent candidate for simd vectorization.
@ -107,3 +109,44 @@ AnimPose boneLookAt(const glm::vec3& target, const AnimPose& bone) {
glm::vec4(bone.trans(), 1.0f));
return AnimPose(lookAt);
}
// This will attempt to determine the proper body facing of a characters body
// assumes headRot is z-forward and y-up.
// and returns a bodyRot that is also z-forward and y-up
glm::quat computeBodyFacingFromHead(const glm::quat& headRot, const glm::vec3& up) {
glm::vec3 bodyUp = glm::normalize(up);
// initially take the body facing from the head.
glm::vec3 headUp = headRot * Vectors::UNIT_Y;
glm::vec3 headForward = headRot * Vectors::UNIT_Z;
glm::vec3 headLeft = headRot * Vectors::UNIT_X;
const float NOD_THRESHOLD = cosf(glm::radians(45.0f));
const float TILT_THRESHOLD = cosf(glm::radians(30.0f));
glm::vec3 bodyForward = headForward;
float nodDot = glm::dot(headForward, bodyUp);
float tiltDot = glm::dot(headLeft, bodyUp);
if (fabsf(tiltDot) < TILT_THRESHOLD) { // if we are not tilting too much
if (nodDot < -NOD_THRESHOLD) { // head is looking downward
// the body should face in the same direction as the top the head.
bodyForward = headUp;
} else if (nodDot > NOD_THRESHOLD) { // head is looking upward
// the body should face away from the top of the head.
bodyForward = -headUp;
}
}
// cancel out upward component
bodyForward = glm::normalize(bodyForward - nodDot * bodyUp);
glm::vec3 u, v, w;
generateBasisVectors(bodyForward, bodyUp, u, v, w);
// create matrix from orthogonal basis vectors
glm::mat4 bodyMat(glm::vec4(w, 0.0f), glm::vec4(v, 0.0f), glm::vec4(u, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
return glmExtractRotation(bodyMat);
}

View file

@ -33,4 +33,9 @@ inline glm::quat safeLerp(const glm::quat& a, const glm::quat& b, float alpha) {
AnimPose boneLookAt(const glm::vec3& target, const AnimPose& bone);
// This will attempt to determine the proper body facing of a characters body
// assumes headRot is z-forward and y-up.
// and returns a bodyRot that is also z-forward and y-up
glm::quat computeBodyFacingFromHead(const glm::quat& headRot, const glm::vec3& up);
#endif

View file

@ -11,6 +11,8 @@
#include "AutoUpdater.h"
#include <BuildInfo.h>
#include <NetworkAccessManager.h>
#include <SharedUtil.h>
#include <unordered_map>
@ -157,10 +159,8 @@ void AutoUpdater::parseLatestVersionData() {
}
void AutoUpdater::checkVersionAndNotify() {
if (QCoreApplication::applicationVersion() == "dev" ||
QCoreApplication::applicationVersion().contains("PR") ||
_builds.empty()) {
// No version checking is required in dev builds or when no build
if (BuildInfo::BUILD_TYPE != BuildInfo::BuildType::Stable || _builds.empty()) {
// No version checking is required in nightly/PR/dev builds or when no build
// data was found for the platform
return;
}
@ -196,4 +196,4 @@ void AutoUpdater::appendBuildData(int versionNumber,
thisBuildDetails.insert("pullRequestNumber", pullRequestNumber);
_builds.insert(versionNumber, thisBuildDetails);
}
}

View file

@ -82,6 +82,7 @@ AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWe
avatar->setSessionUUID(sessionUUID);
avatar->setOwningAvatarMixer(mixerWeakPointer);
// addAvatar is only called from newOrExistingAvatar, which already locks _hashLock
_avatarHash.insert(sessionUUID, avatar);
emit avatarAddedEvent(sessionUUID);

View file

@ -46,7 +46,7 @@ class AvatarHashMap : public QObject, public Dependency {
public:
AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; }
const AvatarHash getHashCopy() const { QReadLocker lock(&_hashLock); return _avatarHash; }
int size() { return _avatarHash.size(); }
int size() { QReadLocker lock(&_hashLock); return _avatarHash.size(); }
// Currently, your own avatar will be included as the null avatar id.
@ -152,8 +152,6 @@ protected:
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason);
AvatarHash _avatarHash;
// "Case-based safety": Most access to the _avatarHash is on the same thread. Write access is protected by a write-lock.
// If you read from a different thread, you must read-lock the _hashLock. (Scripted write access is not supported).
mutable QReadWriteLock _hashLock;
private:

View file

@ -157,18 +157,19 @@ void TextureBaker::processTexture() {
return;
}
const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat());
if (name == nullptr) {
handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString());
return;
}
// attempt to write the baked texture to the destination file path
{
if (memKTX->_header.isCompressed()) {
const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat());
if (name == nullptr) {
handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString());
return;
}
const char* data = reinterpret_cast<const char*>(memKTX->_storage->data());
const size_t length = memKTX->_storage->size();
auto fileName = _baseFilename + BAKED_TEXTURE_BCN_SUFFIX;
auto fileName = _baseFilename + "_" + name + ".ktx";
auto filePath = _outputDirectory.absoluteFilePath(fileName);
QFile bakedTextureFile { filePath };
if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) {
@ -177,6 +178,18 @@ void TextureBaker::processTexture() {
}
_outputFiles.push_back(filePath);
meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = _metaTexturePathPrefix + fileName;
} else {
const char* data = reinterpret_cast<const char*>(memKTX->_storage->data());
const size_t length = memKTX->_storage->size();
auto fileName = _baseFilename + ".ktx";
auto filePath = _outputDirectory.absoluteFilePath(fileName);
QFile ktxTextureFile { filePath };
if (!ktxTextureFile.open(QIODevice::WriteOnly) || ktxTextureFile.write(data, length) == -1) {
handleError("Could not write ktx texture for " + _textureURL.toString());
return;
}
_outputFiles.push_back(filePath);
}

View file

@ -206,7 +206,7 @@ private:
render::ItemKey _itemKey { render::ItemKey::Builder().withTypeMeta() };
bool _didLastVisualGeometryRequestSucceed { false };
bool _didLastVisualGeometryRequestSucceed { true };
void processMaterials();
};

View file

@ -37,8 +37,8 @@ layout(std140) uniform particleBuffer {
ParticleUniforms particle;
};
in vec3 inPosition;
in vec2 inColor; // This is actual Lifetime + Seed
layout(location=0) in vec3 inPosition;
layout(location=2) in vec2 inColor; // This is actual Lifetime + Seed
out vec4 varColor;
out vec2 varTexcoord;

View file

@ -350,6 +350,8 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) {
if (idxMoveStartingPointCandidate != -1) {
_moveCurrentTouchId = tPoints[idxMoveStartingPointCandidate].id();
_unusedTouches.erase(_moveCurrentTouchId);
thisPoint.x = tPoints[idxMoveStartingPointCandidate].pos().x();
thisPoint.y = tPoints[idxMoveStartingPointCandidate].pos().y();
moveTouchBegin(thisPoint);
} else {
moveTouchEnd();
@ -359,6 +361,8 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) {
if (idxViewStartingPointCandidate != -1) {
_viewCurrentTouchId = tPoints[idxViewStartingPointCandidate].id();
_unusedTouches.erase(_viewCurrentTouchId);
thisPoint.x = tPoints[idxViewStartingPointCandidate].pos().x();
thisPoint.y = tPoints[idxViewStartingPointCandidate].pos().y();
viewTouchBegin(thisPoint);
} else {
viewTouchEnd();
@ -368,6 +372,8 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) {
if (idxJumpStartingPointCandidate != -1) {
_jumpCurrentTouchId = tPoints[idxJumpStartingPointCandidate].id();
_unusedTouches.erase(_jumpCurrentTouchId);
thisPoint.x = tPoints[idxJumpStartingPointCandidate].pos().x();
thisPoint.y = tPoints[idxJumpStartingPointCandidate].pos().y();
jumpTouchBegin(thisPoint);
} else {
if (_jumpHasValidTouch) {
@ -424,6 +430,7 @@ void TouchscreenVirtualPadDevice::moveTouchBegin(glm::vec2 touchPoint) {
} else {
_moveRefTouchPoint = touchPoint;
}
_moveCurrentTouchPoint = touchPoint;
_moveHasValidTouch = true;
}
}

View file

@ -157,7 +157,11 @@ void AddressManager::storeCurrentAddress() {
// be loaded over http(s)
// url.scheme() == URL_SCHEME_HTTP ||
// url.scheme() == URL_SCHEME_HTTPS ||
currentAddressHandle.set(url);
if (isConnected()) {
currentAddressHandle.set(url);
} else {
qCWarning(networking) << "Ignoring attempt to save current address because not connected to domain:" << url;
}
} else {
qCWarning(networking) << "Ignoring attempt to save current address with an invalid url:" << url;
}

View file

@ -636,7 +636,10 @@ void Resource::attemptRequest() {
<< "- retrying asset load - attempt" << _attempts << " of " << MAX_ATTEMPTS;
}
ResourceCache::attemptRequest(_self);
auto self = _self.lock();
if (self) {
ResourceCache::attemptRequest(self);
}
}
void Resource::finishedLoading(bool success) {

View file

@ -38,6 +38,11 @@ ResourceManager::ResourceManager(bool atpSupportEnabled) : _atpSupportEnabled(at
_thread.start();
}
ResourceManager::~ResourceManager() {
_thread.terminate();
_thread.wait();
}
void ResourceManager::setUrlPrefixOverride(const QString& prefix, const QString& replacement) {
QMutexLocker locker(&_prefixMapLock);
if (replacement.isEmpty()) {

View file

@ -28,6 +28,7 @@ class ResourceManager: public QObject, public Dependency {
public:
ResourceManager(bool atpSupportEnabled = true);
~ResourceManager();
void setUrlPrefixOverride(const QString& prefix, const QString& replacement);
QString normalizeURL(const QString& urlString);

View file

@ -40,7 +40,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::AvatarData:
case PacketType::BulkAvatarData:
case PacketType::KillAvatar:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::FBXReaderNodeReparenting);
return static_cast<PacketVersion>(AvatarMixerPacketVersion::FixMannequinDefaultAvatarFeet);
case PacketType::MessagesData:
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
// ICE packets

View file

@ -282,7 +282,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
AvatarIdentityLookAtSnapping,
UpdatedMannequinDefaultAvatar,
AvatarJointDefaultPoseFlags,
FBXReaderNodeReparenting
FBXReaderNodeReparenting,
FixMannequinDefaultAvatarFeet
};
enum class DomainConnectRequestVersion : PacketVersion {

View file

@ -40,9 +40,10 @@ bool readOctreeFile(QString path, QJsonDocument* doc) {
}
bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromJSON(QJsonObject root) {
if (root.contains("Id") && root.contains("DataVersion")) {
if (root.contains("Id") && root.contains("DataVersion") && root.contains("Version")) {
id = root["Id"].toVariant().toUuid();
version = root["DataVersion"].toInt();
dataVersion = root["DataVersion"].toInt();
version = root["Version"].toInt();
}
readSubclassData(root);
return true;
@ -76,11 +77,10 @@ bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromFile(QString path) {
}
QByteArray OctreeUtils::RawOctreeData::toByteArray() {
const auto protocolVersion = (int)versionForPacketType((PacketTypeEnum::Value)dataPacketType());
QJsonObject obj {
{ "DataVersion", QJsonValue((qint64)version) },
{ "DataVersion", QJsonValue((qint64)dataVersion) },
{ "Id", QJsonValue(id.toString()) },
{ "Version", protocolVersion },
{ "Version", QJsonValue((qint64)version) },
};
writeSubclassData(obj);
@ -111,8 +111,8 @@ PacketType OctreeUtils::RawOctreeData::dataPacketType() const {
void OctreeUtils::RawOctreeData::resetIdAndVersion() {
id = QUuid::createUuid();
version = OctreeUtils::INITIAL_VERSION;
qDebug() << "Reset octree data to: " << id << version;
dataVersion = OctreeUtils::INITIAL_VERSION;
qDebug() << "Reset octree data to: " << id << dataVersion;
}
void OctreeUtils::RawEntityData::readSubclassData(const QJsonObject& root) {

View file

@ -28,6 +28,7 @@ constexpr Version INITIAL_VERSION = 0;
class RawOctreeData {
public:
QUuid id { QUuid() };
Version dataVersion { -1 };
Version version { -1 };
virtual PacketType dataPacketType() const;

View file

@ -179,8 +179,8 @@ bool OctreePersistThread::process() {
OctreeUtils::RawOctreeData data;
if (data.readOctreeDataInfoFromFile(_filename)) {
qDebug() << "Setting entity version info to: " << data.id << data.version;
_tree->setOctreeVersionInfo(data.id, data.version);
qDebug() << "Setting entity version info to: " << data.id << data.dataVersion;
_tree->setOctreeVersionInfo(data.id, data.dataVersion);
}
bool persistentFileRead;

View file

@ -19,7 +19,9 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render:
// Warning : the cull functor passed to the shadow pass should only be testing for LOD culling. If frustum culling
// is performed, then casters not in the view frustum will be removed, which is not what we wish.
task.addJob<RenderShadowTask>("RenderShadowTask", cullFunctor, tagBits, tagMask);
if (isDeferred) {
task.addJob<RenderShadowTask>("RenderShadowTask", cullFunctor, tagBits, tagMask);
}
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor, tagBits, tagMask);
assert(items.canCast<RenderFetchCullSortTask::Output>());

View file

@ -419,7 +419,7 @@ void ScriptEngine::waitTillDoneRunning() {
// Wait for the scripting thread to stop running, as
// flooding it with aborts/exceptions will persist it longer
static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MSECS_PER_SECOND;
if (workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) {
if (!workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) {
workerThread->terminate();
}
}

View file

@ -20,6 +20,16 @@ const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters
const float DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD = 0.185f; // meters
const float DEFAULT_AVATAR_NECK_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD;
const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f;
const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f;
const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f;
const float DEFAULT_AVATAR_SUPPORT_BASE_BACK = 0.10f;
const float DEFAULT_AVATAR_FORWARD_DAMPENING_FACTOR = 0.5f;
const float DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR = 2.0f;
const float DEFAULT_AVATAR_HIPS_MASS = 40.0f;
const float DEFAULT_AVATAR_HEAD_MASS = 20.0f;
const float DEFAULT_AVATAR_LEFTHAND_MASS = 2.0f;
const float DEFAULT_AVATAR_RIGHTHAND_MASS = 2.0f;
// Used when avatar is missing joints... (avatar space)
const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 };

View file

@ -574,8 +574,9 @@ void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& seconda
vAxisOut = glm::cross(wAxisOut, uAxisOut);
}
// assumes z-forward and y-up
glm::vec2 getFacingDir2D(const glm::quat& rot) {
glm::vec3 facing3D = rot * Vectors::UNIT_NEG_Z;
glm::vec3 facing3D = rot * Vectors::UNIT_Z;
glm::vec2 facing2D(facing3D.x, facing3D.z);
const float ALMOST_ZERO = 0.0001f;
if (glm::length(facing2D) < ALMOST_ZERO) {
@ -585,8 +586,9 @@ glm::vec2 getFacingDir2D(const glm::quat& rot) {
}
}
// assumes z-forward and y-up
glm::vec2 getFacingDir2D(const glm::mat4& m) {
glm::vec3 facing3D = transformVectorFast(m, Vectors::UNIT_NEG_Z);
glm::vec3 facing3D = transformVectorFast(m, Vectors::UNIT_Z);
glm::vec2 facing2D(facing3D.x, facing3D.z);
const float ALMOST_ZERO = 0.0001f;
if (glm::length(facing2D) < ALMOST_ZERO) {

View file

@ -250,7 +250,10 @@ glm::vec3 transformVectorFull(const glm::mat4& m, const glm::vec3& v);
void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& secondaryAxis,
glm::vec3& uAxisOut, glm::vec3& vAxisOut, glm::vec3& wAxisOut);
// assumes z-forward and y-up
glm::vec2 getFacingDir2D(const glm::quat& rot);
// assumes z-forward and y-up
glm::vec2 getFacingDir2D(const glm::mat4& m);
inline bool isNaN(const glm::vec3& value) { return isNaN(value.x) || isNaN(value.y) || isNaN(value.z); }

View file

@ -431,6 +431,19 @@ bool TabletProxy::isMessageDialogOpen() {
return result.toBool();
}
void TabletProxy::closeDialog() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "closeDialog");
return;
}
if (!_qmlTabletRoot) {
return;
}
QMetaObject::invokeMethod(_qmlTabletRoot, "closeDialog");
}
void TabletProxy::emitWebEvent(const QVariant& msg) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "emitWebEvent", Q_ARG(QVariant, msg));

View file

@ -308,6 +308,12 @@ public:
*/
Q_INVOKABLE bool isMessageDialogOpen();
/**jsdoc
* Close any open dialogs.
* @function TabletProxy#closeDialog
*/
Q_INVOKABLE void closeDialog();
/**jsdoc
* Creates a new button, adds it to this and returns it.
* @function TabletProxy#addButton

View file

@ -47,7 +47,7 @@ static_assert(
const char* SDL2Manager::NAME = "SDL2";
const char* SDL2Manager::SDL2_ID_STRING = "SDL2";
const bool DEFAULT_ENABLED = false;
const bool DEFAULT_ENABLED = true;
SDL_JoystickID SDL2Manager::getInstanceId(SDL_GameController* controller) {
SDL_Joystick* joystick = SDL_GameControllerGetJoystick(controller);

View file

@ -15,19 +15,25 @@
#include "OculusHelpers.h"
using namespace hifi;
void OculusBaseDisplayPlugin::resetSensors() {
ovr_RecenterTrackingOrigin(_session);
_currentRenderFrameInfo.renderPose = glm::mat4(); // identity
}
bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
handleOVREvents();
if (quitRequested()) {
ovrSessionStatus status{};
if (!OVR_SUCCESS(ovr_GetSessionStatus(_session, &status))) {
qCWarning(oculusLog) << "Unable to fetch Oculus session status" << ovr::getError();
return false;
}
if (ovr::quitRequested(status)) {
QMetaObject::invokeMethod(qApp, "quit");
return false;
}
if (reorientRequested()) {
if (ovr::reorientRequested(status)) {
emit resetSensorsRequested();
}
@ -35,18 +41,18 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
_currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds();
_currentRenderFrameInfo.predictedDisplayTime = ovr_GetPredictedDisplayTime(_session, frameIndex);
auto trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrTrue);
_currentRenderFrameInfo.renderPose = toGlm(trackingState.HeadPose.ThePose);
_currentRenderFrameInfo.renderPose = ovr::toGlm(trackingState.HeadPose.ThePose);
_currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose;
std::array<glm::mat4, 2> handPoses;
// Make controller poses available to the presentation thread
ovr_for_each_hand([&](ovrHandType hand) {
ovr::for_each_hand([&](ovrHandType hand) {
static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked | ovrStatus_PositionTracked;
if (REQUIRED_HAND_STATUS != (trackingState.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) {
return;
}
auto correctedPose = ovrControllerPoseToHandPose(hand, trackingState.HandPoses[hand]);
auto correctedPose = ovr::toControllerPose(hand, trackingState.HandPoses[hand]);
static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y);
handPoses[hand] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION);
});
@ -58,7 +64,7 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
}
bool OculusBaseDisplayPlugin::isSupported() const {
return oculusAvailable();
return ovr::available();
}
glm::mat4 OculusBaseDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const {
@ -71,7 +77,7 @@ glm::mat4 OculusBaseDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& ba
ovrFovPort fovPort = _hmdDesc.DefaultEyeFov[eye];
ovrEyeRenderDesc& erd = ovr_GetRenderDesc(_session, ovrEye, fovPort);
ovrMatrix4f ovrPerspectiveProjection = ovrMatrix4f_Projection(erd.Fov, baseNearClip, baseFarClip, ovrProjection_ClipRangeOpenGL);
return toGlm(ovrPerspectiveProjection);
return ovr::toGlm(ovrPerspectiveProjection);
} else {
return baseProjection;
}
@ -85,7 +91,7 @@ glm::mat4 OculusBaseDisplayPlugin::getCullingProjection(const glm::mat4& basePro
float baseFarClip = baseFrustum.getFarClip();
auto combinedFov = _eyeFovs[0];
combinedFov.LeftTan = combinedFov.RightTan = std::max(combinedFov.LeftTan, combinedFov.RightTan);
return toGlm(ovrMatrix4f_Projection(combinedFov, baseNearClip, baseFarClip, ovrProjection_ClipRangeOpenGL));
return ovr::toGlm(ovrMatrix4f_Projection(combinedFov, baseNearClip, baseFarClip, ovrProjection_ClipRangeOpenGL));
} else {
return baseProjection;
}
@ -102,7 +108,7 @@ void OculusBaseDisplayPlugin::uncustomizeContext() {
}
bool OculusBaseDisplayPlugin::internalActivate() {
_session = acquireOculusSession();
_session = ovr::acquireRenderSession();
if (!_session) {
return false;
}
@ -113,21 +119,21 @@ bool OculusBaseDisplayPlugin::internalActivate() {
_viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f;
_ipd = 0;
ovr_for_each_eye([&](ovrEyeType eye) {
ovr::for_each_eye([&](ovrEyeType eye) {
_eyeFovs[eye] = _hmdDesc.DefaultEyeFov[eye];
ovrEyeRenderDesc& erd = _eyeRenderDescs[eye] = ovr_GetRenderDesc(_session, eye, _eyeFovs[eye]);
ovrMatrix4f ovrPerspectiveProjection =
ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_ClipRangeOpenGL);
_eyeProjections[eye] = toGlm(ovrPerspectiveProjection);
_eyeOffsets[eye] = glm::translate(mat4(), toGlm(erd.HmdToEyeOffset));
eyeSizes[eye] = toGlm(ovr_GetFovTextureSize(_session, eye, erd.Fov, 1.0f));
_viewScaleDesc.HmdToEyeOffset[eye] = erd.HmdToEyeOffset;
_ipd += glm::abs(glm::length(toGlm(erd.HmdToEyeOffset)));
_eyeProjections[eye] = ovr::toGlm(ovrPerspectiveProjection);
_eyeOffsets[eye] = ovr::toGlm(erd.HmdToEyePose);
eyeSizes[eye] = ovr::toGlm(ovr_GetFovTextureSize(_session, eye, erd.Fov, 1.0f));
_viewScaleDesc.HmdToEyePose[eye] = erd.HmdToEyePose;
_ipd += glm::abs(glm::length(ovr::toGlm(erd.HmdToEyePose.Position)));
});
auto combinedFov = _eyeFovs[0];
combinedFov.LeftTan = combinedFov.RightTan = std::max(combinedFov.LeftTan, combinedFov.RightTan);
_cullingProjection = toGlm(ovrMatrix4f_Projection(combinedFov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_ClipRangeOpenGL));
_cullingProjection = ovr::toGlm(ovrMatrix4f_Projection(combinedFov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_ClipRangeOpenGL));
_renderTargetSize = uvec2(
eyeSizes[0].x + eyeSizes[1].x,
@ -136,7 +142,7 @@ bool OculusBaseDisplayPlugin::internalActivate() {
memset(&_sceneLayer, 0, sizeof(ovrLayerEyeFov));
_sceneLayer.Header.Type = ovrLayerType_EyeFov;
_sceneLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft;
ovr_for_each_eye([&](ovrEyeType eye) {
ovr::for_each_eye([&](ovrEyeType eye) {
ovrFovPort & fov = _sceneLayer.Fov[eye] = _eyeRenderDescs[eye].Fov;
ovrSizei & size = _sceneLayer.Viewport[eye].Size = ovr_GetFovTextureSize(_session, eye, fov, 1.0f);
_sceneLayer.Viewport[eye].Pos = { eye == ovrEye_Left ? 0 : size.w, 0 };
@ -150,28 +156,14 @@ bool OculusBaseDisplayPlugin::internalActivate() {
void OculusBaseDisplayPlugin::internalDeactivate() {
Parent::internalDeactivate();
ovr::releaseRenderSession(_session);
}
bool OculusBaseDisplayPlugin::activateStandBySession() {
if (!_session) {
_session = acquireOculusSession();
}
return _session;
}
void OculusBaseDisplayPlugin::deactivateSession() {
// FIXME
// Switching to Qt 5.9 exposed a race condition or similar issue that caused a crash when putting on an Rift
// while already in VR mode. Commenting these out is a workaround.
//releaseOculusSession();
//_session = nullptr;
}
void OculusBaseDisplayPlugin::updatePresentPose() {
ovrTrackingState trackingState;
_currentPresentFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds();
_currentPresentFrameInfo.predictedDisplayTime = ovr_GetPredictedDisplayTime(_session, 0);
auto trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrFalse);
_currentPresentFrameInfo.presentPose = toGlm(trackingState.HeadPose.ThePose);
trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrFalse);
_currentPresentFrameInfo.presentPose = ovr::toGlm(trackingState.HeadPose.ThePose);
_currentPresentFrameInfo.renderPose = _currentPresentFrameInfo.presentPose;
}
OculusBaseDisplayPlugin::~OculusBaseDisplayPlugin() {
}

View file

@ -16,37 +16,32 @@
class OculusBaseDisplayPlugin : public HmdDisplayPlugin {
using Parent = HmdDisplayPlugin;
public:
~OculusBaseDisplayPlugin();
bool isSupported() const override;
bool hasAsyncReprojection() const override { return true; }
bool getSupportsAutoSwitch() override final { return true; }
glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override;
glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override;
bool hasAsyncReprojection() const override { return true; }
// Stereo specific methods
void resetSensors() override final;
bool beginFrameRender(uint32_t frameIndex) override;
float getTargetFrameRate() const override { return _hmdDesc.DisplayRefreshRate; }
bool getSupportsAutoSwitch() override final { return true; }
protected:
void customizeContext() override;
void uncustomizeContext() override;
bool internalActivate() override;
void internalDeactivate() override;
bool activateStandBySession() override;
void deactivateSession() override;
void updatePresentPose() override;
protected:
ovrSession _session { nullptr };
ovrSession _session{ nullptr };
ovrGraphicsLuid _luid;
ovrEyeRenderDesc _eyeRenderDescs[2];
ovrFovPort _eyeFovs[2];
std::array<ovrEyeRenderDesc, 2> _eyeRenderDescs;
std::array<ovrFovPort, 2> _eyeFovs;
ovrHmdDesc _hmdDesc;
ovrLayerEyeFov _sceneLayer;
ovrViewScaleDesc _viewScaleDesc;
// ovrLayerEyeFovDepth _depthLayer;
};

View file

@ -22,34 +22,23 @@
#include <NumericalConstants.h>
#include <StreamUtils.h>
#include <OVR_CAPI.h>
#include <ovr_capi.h>
#include "OculusHelpers.h"
Q_DECLARE_LOGGING_CATEGORY(oculus)
static const char* MENU_PARENT = "Avatar";
static const char* MENU_NAME = "Oculus Touch Controllers";
static const char* MENU_PATH = "Avatar" ">" "Oculus Touch Controllers";
using namespace hifi;
const char* OculusControllerManager::NAME = "Oculus";
const quint64 LOST_TRACKING_DELAY = 3000000;
bool OculusControllerManager::isSupported() const {
return oculusAvailable();
return hifi::ovr::available();
}
bool OculusControllerManager::activate() {
InputPlugin::activate();
if (!_session) {
_session = acquireOculusSession();
}
Q_ASSERT(_session);
checkForConnectedDevices();
return true;
}
@ -58,33 +47,30 @@ void OculusControllerManager::checkForConnectedDevices() {
return;
}
unsigned int controllerConnected = ovr_GetConnectedControllerTypes(_session);
ovr::withSession([&] (ovrSession session) {
unsigned int controllerConnected = ovr_GetConnectedControllerTypes(session);
if (!_remote && (controllerConnected & ovrControllerType_Remote) == ovrControllerType_Remote) {
if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Remote, &_inputState))) {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
_remote = std::make_shared<RemoteDevice>(*this);
userInputMapper->registerDevice(_remote);
if (!_remote && (controllerConnected & ovrControllerType_Remote) == ovrControllerType_Remote) {
if (OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Remote, &_inputState))) {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
_remote = std::make_shared<RemoteDevice>(*this);
userInputMapper->registerDevice(_remote);
}
}
}
if (!_touch && (controllerConnected & ovrControllerType_Touch) != 0) {
if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Touch, &_inputState))) {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
_touch = std::make_shared<TouchDevice>(*this);
userInputMapper->registerDevice(_touch);
if (!_touch && (controllerConnected & ovrControllerType_Touch) != 0) {
if (OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Touch, &_inputState))) {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
_touch = std::make_shared<TouchDevice>(*this);
userInputMapper->registerDevice(_touch);
}
}
}
});
}
void OculusControllerManager::deactivate() {
InputPlugin::deactivate();
if (_session) {
releaseOculusSession();
_session = nullptr;
}
// unregister with UserInputMapper
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
if (_touch) {
@ -100,20 +86,30 @@ void OculusControllerManager::pluginUpdate(float deltaTime, const controller::In
checkForConnectedDevices();
if (_touch) {
if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Touch, &_inputState))) {
_touch->update(deltaTime, inputCalibrationData);
} else {
qCWarning(oculus) << "Unable to read Oculus touch input state";
bool updateRemote = false, updateTouch = false;
ovr::withSession([&](ovrSession session) {
if (_touch) {
updateTouch = OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Touch, &_inputState));
if (!updateTouch) {
qCWarning(oculusLog) << "Unable to read Oculus touch input state" << ovr::getError();
}
}
if (_remote) {
updateRemote = OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Remote, &_inputState));
if (!updateRemote) {
qCWarning(oculusLog) << "Unable to read Oculus remote input state" << ovr::getError();
}
}
});
if (_touch && updateTouch) {
_touch->update(deltaTime, inputCalibrationData);
}
if (_remote) {
if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Remote, &_inputState))) {
_remote->update(deltaTime, inputCalibrationData);
} else {
qCWarning(oculus) << "Unable to read Oculus remote input state";
}
if (_remote && updateRemote) {
_remote->update(deltaTime, inputCalibrationData);
}
}
@ -210,15 +206,6 @@ void OculusControllerManager::RemoteDevice::focusOutEvent() {
_buttonPressedMap.clear();
}
bool OculusControllerManager::isHeadControllerMounted() const {
ovrSessionStatus status;
bool success = OVR_SUCCESS(ovr_GetSessionStatus(_session, &status));
if (!success) {
return false;
}
return status.HmdMounted == ovrTrue;
}
void OculusControllerManager::TouchDevice::update(float deltaTime,
const controller::InputCalibrationData& inputCalibrationData) {
_buttonPressedMap.clear();
@ -226,10 +213,19 @@ void OculusControllerManager::TouchDevice::update(float deltaTime,
int numTrackedControllers = 0;
quint64 currentTime = usecTimestampNow();
static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked | ovrStatus_PositionTracked;
auto tracking = ovr_GetTrackingState(_parent._session, 0, false);
ovr_for_each_hand([&](ovrHandType hand) {
bool hasInputFocus = ovr::hasInputFocus();
auto tracking = ovr::getTrackingState(); // ovr_GetTrackingState(_parent._session, 0, false);
ovr::for_each_hand([&](ovrHandType hand) {
++numTrackedControllers;
int controller = (hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND);
// Disable hand tracking while in Oculus Dash (Dash renders it's own hands)
if (!hasInputFocus) {
_poseStateMap.erase(controller);
_poseStateMap[controller].valid = false;
return;
}
if (REQUIRED_HAND_STATUS == (tracking.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) {
_poseStateMap.erase(controller);
handlePose(deltaTime, inputCalibrationData, hand, tracking.HandPoses[hand]);
@ -253,7 +249,7 @@ void OculusControllerManager::TouchDevice::update(float deltaTime,
handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HandPoses[hand]);
});
if (_parent.isHeadControllerMounted()) {
if (ovr::hmdMounted()) {
handleHeadPose(deltaTime, inputCalibrationData, tracking.HeadPose);
} else {
_poseStateMap[controller::HEAD].valid = false;
@ -311,7 +307,7 @@ void OculusControllerManager::TouchDevice::handlePose(float deltaTime,
ovrHandType hand, const ovrPoseStatef& handPose) {
auto poseId = hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND;
auto& pose = _poseStateMap[poseId];
pose = ovrControllerPoseToHandPose(hand, handPose);
pose = ovr::toControllerPose(hand, handPose);
// transform into avatar frame
glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
pose = pose.transform(controllerToAvatar);
@ -320,15 +316,15 @@ void OculusControllerManager::TouchDevice::handlePose(float deltaTime,
void OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime,
const controller::InputCalibrationData& inputCalibrationData,
const ovrPoseStatef& headPose) {
glm::mat4 mat = createMatFromQuatAndPos(toGlm(headPose.ThePose.Orientation),
toGlm(headPose.ThePose.Position));
glm::mat4 mat = createMatFromQuatAndPos(ovr::toGlm(headPose.ThePose.Orientation),
ovr::toGlm(headPose.ThePose.Position));
//perform a 180 flip to make the HMD face the +z instead of -z, beacuse the head faces +z
glm::mat4 matYFlip = mat * Matrices::Y_180;
controller::Pose pose(extractTranslation(matYFlip),
glmExtractRotation(matYFlip),
toGlm(headPose.LinearVelocity), // XXX * matYFlip ?
toGlm(headPose.AngularVelocity));
ovr::toGlm(headPose.LinearVelocity), // XXX * matYFlip ?
ovr::toGlm(headPose.AngularVelocity));
glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) *
@ -343,7 +339,7 @@ void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const
auto poseId = (hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND);
auto& pose = _poseStateMap[poseId];
auto lastHandPose = _lastControllerPose[poseId];
pose = ovrControllerRotationToHandRotation(hand, handPose, lastHandPose);
pose = ovr::toControllerPose(hand, handPose, lastHandPose);
glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
pose = pose.transform(controllerToAvatar);
}
@ -351,36 +347,40 @@ void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const
bool OculusControllerManager::TouchDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) {
Locker locker(_lock);
bool toReturn = true;
if (hand == controller::BOTH || hand == controller::LEFT) {
if (strength == 0.0f) {
_leftHapticStrength = 0.0f;
_leftHapticDuration = 0.0f;
} else {
_leftHapticStrength = (duration > _leftHapticDuration) ? strength : _leftHapticStrength;
if (ovr_SetControllerVibration(_parent._session, ovrControllerType_LTouch, 1.0f, _leftHapticStrength) != ovrSuccess) {
toReturn = false;
ovr::withSession([&](ovrSession session) {
if (hand == controller::BOTH || hand == controller::LEFT) {
if (strength == 0.0f) {
_leftHapticStrength = 0.0f;
_leftHapticDuration = 0.0f;
} else {
_leftHapticStrength = (duration > _leftHapticDuration) ? strength : _leftHapticStrength;
if (ovr_SetControllerVibration(session, ovrControllerType_LTouch, 1.0f, _leftHapticStrength) != ovrSuccess) {
toReturn = false;
}
_leftHapticDuration = std::max(duration, _leftHapticDuration);
}
_leftHapticDuration = std::max(duration, _leftHapticDuration);
}
}
if (hand == controller::BOTH || hand == controller::RIGHT) {
if (strength == 0.0f) {
_rightHapticStrength = 0.0f;
_rightHapticDuration = 0.0f;
} else {
_rightHapticStrength = (duration > _rightHapticDuration) ? strength : _rightHapticStrength;
if (ovr_SetControllerVibration(_parent._session, ovrControllerType_RTouch, 1.0f, _rightHapticStrength) != ovrSuccess) {
toReturn = false;
if (hand == controller::BOTH || hand == controller::RIGHT) {
if (strength == 0.0f) {
_rightHapticStrength = 0.0f;
_rightHapticDuration = 0.0f;
} else {
_rightHapticStrength = (duration > _rightHapticDuration) ? strength : _rightHapticStrength;
if (ovr_SetControllerVibration(session, ovrControllerType_RTouch, 1.0f, _rightHapticStrength) != ovrSuccess) {
toReturn = false;
}
_rightHapticDuration = std::max(duration, _rightHapticDuration);
}
_rightHapticDuration = std::max(duration, _rightHapticDuration);
}
}
});
return toReturn;
}
void OculusControllerManager::TouchDevice::stopHapticPulse(bool leftHand) {
auto handType = (leftHand ? ovrControllerType_LTouch : ovrControllerType_RTouch);
ovr_SetControllerVibration(_parent._session, handType, 0.0f, 0.0f);
ovr::withSession([&](ovrSession session) {
ovr_SetControllerVibration(session, handType, 0.0f, 0.0f);
});
}
/**jsdoc

View file

@ -26,10 +26,8 @@ public:
// Plugin functions
bool isSupported() const override;
const QString getName() const override { return NAME; }
bool isHandController() const override { return _touch != nullptr; }
bool isHeadController() const override { return true; }
bool isHeadControllerMounted() const;
QStringList getSubdeviceNames() override;
bool activate() override;
@ -105,7 +103,6 @@ private:
void checkForConnectedDevices();
ovrSession _session { nullptr };
ovrInputState _inputState {};
RemoteDevice::Pointer _remote;
TouchDevice::Pointer _touch;

View file

@ -19,6 +19,8 @@
#include "OculusHelpers.h"
using namespace hifi;
const char* OculusDisplayPlugin::NAME { "Oculus Rift" };
static ovrPerfHudMode currentDebugMode = ovrPerfHud_Off;
@ -63,7 +65,7 @@ void OculusDisplayPlugin::cycleDebugOutput() {
void OculusDisplayPlugin::customizeContext() {
Parent::customizeContext();
_outputFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OculusOutput", gpu::Element::COLOR_SRGBA_32, _renderTargetSize.x, _renderTargetSize.y));
_outputFramebuffer.reset(gpu::Framebuffer::create("OculusOutput", gpu::Element::COLOR_SRGBA_32, _renderTargetSize.x, _renderTargetSize.y));
ovrTextureSwapChainDesc desc = { };
desc.Type = ovrTexture_2D;
desc.ArraySize = 1;
@ -76,14 +78,14 @@ void OculusDisplayPlugin::customizeContext() {
ovrResult result = ovr_CreateTextureSwapChainGL(_session, &desc, &_textureSwapChain);
if (!OVR_SUCCESS(result)) {
logCritical("Failed to create swap textures");
qCritical(oculusLog) << "Failed to create swap textures" << ovr::getError();
return;
}
int length = 0;
result = ovr_GetTextureSwapChainLength(_session, _textureSwapChain, &length);
if (!OVR_SUCCESS(result) || !length) {
logCritical("Unable to count swap chain textures");
qCritical(oculusLog) << "Unable to count swap chain textures" << ovr::getError();
return;
}
for (int i = 0; i < length; ++i) {
@ -164,8 +166,8 @@ void OculusDisplayPlugin::hmdPresent() {
auto result = ovr_CommitTextureSwapChain(_session, _textureSwapChain);
Q_ASSERT(OVR_SUCCESS(result));
_sceneLayer.SensorSampleTime = _currentPresentFrameInfo.sensorSampleTime;
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose);
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose);
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovr::poseFromGlm(_currentPresentFrameInfo.renderPose);
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovr::poseFromGlm(_currentPresentFrameInfo.renderPose);
auto submitStart = usecTimestampNow();
uint64_t nonSubmitInterval = 0;
@ -192,7 +194,7 @@ void OculusDisplayPlugin::hmdPresent() {
}
if (!OVR_SUCCESS(result)) {
logWarning("Failed to present");
qWarning(oculusLog) << "Failed to present" << ovr::getError();
}
static int compositorDroppedFrames = 0;
@ -234,9 +236,7 @@ QJsonObject OculusDisplayPlugin::getHardwareStats() const {
}
bool OculusDisplayPlugin::isHmdMounted() const {
ovrSessionStatus status;
return (OVR_SUCCESS(ovr_GetSessionStatus(_session, &status)) &&
(ovrFalse != status.HmdMounted));
return ovr::hmdMounted();
}
QString OculusDisplayPlugin::getPreferredAudioInDevice() const {

View file

@ -24,41 +24,17 @@
#include <NumericalConstants.h>
Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display")
Q_LOGGING_CATEGORY(oculus, "hifi.plugins.display.oculus")
static std::atomic<uint32_t> refCount { 0 };
static ovrSession session { nullptr };
static bool _quitRequested { false };
static bool _reorientRequested { false };
inline ovrErrorInfo getError() {
ovrErrorInfo error;
ovr_GetLastErrorInfo(&error);
return error;
}
void logWarning(const char* what) {
qWarning(oculus) << what << ":" << getError().ErrorString;
}
void logCritical(const char* what) {
std::string error("[oculus] ");
error += what;
error += ": ";
error += getError().ErrorString;
qCritical(error.c_str());
}
Q_LOGGING_CATEGORY(oculusLog, "hifi.plugins.display.oculus")
using namespace hifi;
static wchar_t* REQUIRED_OCULUS_DLL = L"LibOVRRT64_1.dll";
static wchar_t FOUND_PATH[MAX_PATH];
bool oculusAvailable() {
bool ovr::available() {
static std::once_flag once;
static bool result { false };
static bool result{ false };
std::call_once(once, [&] {
static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR");
static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG);
if (enableDebugOpenVR) {
@ -81,113 +57,107 @@ bool oculusAvailable() {
return result;
}
ovrSession acquireOculusSession() {
if (!session && !oculusAvailable()) {
qCDebug(oculus) << "oculus: no runtime or HMD present";
class ovrImpl {
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
std::mutex mutex;
ovrSession session{ nullptr };
size_t renderCount{ 0 };
private:
void setupSession(bool render) {
if (session) {
return;
}
ovrInitParams initParams{ ovrInit_RequestVersion | ovrInit_FocusAware, OVR_MINOR_VERSION, nullptr, 0, 0 };
if (render) {
initParams.Flags |= ovrInit_MixedRendering;
} else {
initParams.Flags |= ovrInit_Invisible;
}
if (!OVR_SUCCESS(ovr_Initialize(&initParams))) {
qCWarning(oculusLog) << "Failed to initialze Oculus SDK" << ovr::getError();
return;
}
ovrGraphicsLuid luid;
if (!OVR_SUCCESS(ovr_Create(&session, &luid))) {
qCWarning(oculusLog) << "Failed to acquire Oculus session" << ovr::getError();
return;
}
}
void releaseSession() {
if (!session) {
return;
}
ovr_Destroy(session);
session = nullptr;
ovr_Shutdown();
}
public:
void withSession(const std::function<void(ovrSession)>& f) {
Lock lock(mutex);
if (!session) {
setupSession(false);
}
f(session);
}
ovrSession acquireRenderSession() {
Lock lock(mutex);
if (renderCount++ == 0) {
releaseSession();
setupSession(true);
}
return session;
}
if (!session) {
ovrInitParams initParams {
ovrInit_RequestVersion | ovrInit_MixedRendering, OVR_MINOR_VERSION, nullptr, 0, 0
};
if (!OVR_SUCCESS(ovr_Initialize(&initParams))) {
logWarning("Failed to initialize Oculus SDK");
return session;
}
#ifdef OCULUS_APP_ID
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
if (ovr_PlatformInitializeWindows(OCULUS_APP_ID) != ovrPlatformInitialize_Success) {
// we were unable to initialize the platform for entitlement check - fail the check
_quitRequested = true;
} else {
qCDebug(oculus) << "Performing Oculus Platform entitlement check";
ovr_Entitlement_GetIsViewerEntitled();
}
}
#endif
Q_ASSERT(0 == refCount);
ovrGraphicsLuid luid;
if (!OVR_SUCCESS(ovr_Create(&session, &luid))) {
logWarning("Failed to acquire Oculus session");
return session;
void releaseRenderSession(ovrSession session) {
Lock lock(mutex);
if (--renderCount == 0) {
releaseSession();
}
}
} _ovr;
++refCount;
return session;
ovrSession ovr::acquireRenderSession() {
return _ovr.acquireRenderSession();
}
void releaseOculusSession() {
Q_ASSERT(refCount > 0 && session);
// HACK the Oculus runtime doesn't seem to play well with repeated shutdown / restart.
// So for now we'll just hold on to the session
#if 0
if (!--refCount) {
qCDebug(oculus) << "oculus: zero refcount, shutdown SDK and session";
ovr_Destroy(session);
ovr_Shutdown();
session = nullptr;
}
#endif
void ovr::releaseRenderSession(ovrSession session) {
_ovr.releaseRenderSession(session);
}
void handleOVREvents() {
if (!session) {
return;
}
void ovr::withSession(const std::function<void(ovrSession)>& f) {
_ovr.withSession(f);
}
ovrSessionStatus status;
if (!OVR_SUCCESS(ovr_GetSessionStatus(session, &status))) {
return;
}
_quitRequested = status.ShouldQuit;
_reorientRequested = status.ShouldRecenter;
#ifdef OCULUS_APP_ID
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
// pop messages to see if we got a return for an entitlement check
ovrMessageHandle message = ovr_PopMessage();
while (message) {
switch (ovr_Message_GetType(message)) {
case ovrMessage_Entitlement_GetIsViewerEntitled: {
if (!ovr_Message_IsError(message)) {
// this viewer is entitled, no need to flag anything
qCDebug(oculus) << "Oculus Platform entitlement check succeeded, proceeding normally";
} else {
// we failed the entitlement check, set our flag so the app can stop
qCDebug(oculus) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID;
_quitRequested = true;
}
}
}
// free the message handle to cleanup and not leak
ovr_FreeMessage(message);
// pop the next message to check, if there is one
message = ovr_PopMessage();
ovrSessionStatus ovr::getStatus() {
ovrSessionStatus status{};
withSession([&](ovrSession session) {
if (!OVR_SUCCESS(ovr_GetSessionStatus(session, &status))) {
qCWarning(oculusLog) << "Failed to get session status" << ovr::getError();
}
}
#endif
});
return status;
}
bool quitRequested() {
return _quitRequested;
}
bool reorientRequested() {
return _reorientRequested;
ovrTrackingState ovr::getTrackingState() {
ovrTrackingState result{};
withSession([&](ovrSession session) { result = ovr_GetTrackingState(session, 0, ovrFalse); });
return result;
}
controller::Pose ovrControllerPoseToHandPose(
ovrHandType hand,
const ovrPoseStatef& handPose) {
QString ovr::getError() {
static ovrErrorInfo error;
ovr_GetLastErrorInfo(&error);
return QString(error.ErrorString);
}
controller::Pose hifi::ovr::toControllerPose(ovrHandType hand, const ovrPoseStatef& handPose) {
// When the sensor-to-world rotation is identity the coordinate axes look like this:
//
// user
@ -247,9 +217,8 @@ controller::Pose ovrControllerPoseToHandPose(
static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand;
static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f,
-CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET * 1.5f);
static const glm::vec3 CONTROLLER_OFFSET =
glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f);
static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
@ -268,12 +237,13 @@ controller::Pose ovrControllerPoseToHandPose(
return pose;
}
controller::Pose ovrControllerRotationToHandRotation(ovrHandType hand, const ovrPoseStatef& handPose,
const ovrPoseStatef& lastHandPose) {
controller::Pose hifi::ovr::toControllerPose(ovrHandType hand,
const ovrPoseStatef& handPose,
const ovrPoseStatef& lastHandPose) {
static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
static const glm::quat touchToHand = yFlip * quarterX;
static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
@ -281,9 +251,8 @@ controller::Pose ovrControllerRotationToHandRotation(ovrHandType hand, const ovr
static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand;
static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f,
-CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET * 1.5f);
static const glm::vec3 CONTROLLER_OFFSET =
glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f);
static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
@ -301,3 +270,52 @@ controller::Pose ovrControllerRotationToHandRotation(ovrHandType hand, const ovr
pose.valid = true;
return pose;
}
// FIXME These should be moved to an oculusPlatform plugin, they don't interact with the controller or session state
#if 0
void handleOVREvents() {
updateSessionStatus(true);
#ifdef OCULUS_APP_ID
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
// pop messages to see if we got a return for an entitlement check
ovrMessageHandle message = ovr_PopMessage();
while (message) {
switch (ovr_Message_GetType(message)) {
case ovrMessage_Entitlement_GetIsViewerEntitled:
{
if (!ovr_Message_IsError(message)) {
// this viewer is entitled, no need to flag anything
qCDebug(oculus) << "Oculus Platform entitlement check succeeded, proceeding normally";
} else {
// we failed the entitlement check, set our flag so the app can stop
qCDebug(oculus) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID;
_quitRequested = true;
}
}
}
// free the message handle to cleanup and not leak
ovr_FreeMessage(message);
// pop the next message to check, if there is one
message = ovr_PopMessage();
}
}
#endif
}
#ifdef OCULUS_APP_ID
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
if (ovr_PlatformInitializeWindows(OCULUS_APP_ID) != ovrPlatformInitialize_Success) {
// we were unable to initialize the platform for entitlement check - fail the check
_quitRequested = true;
} else {
qCDebug(oculusLog) << "Performing Oculus Platform entitlement check";
ovr_Entitlement_GetIsViewerEntitled();
}
}
#endif
#endif

View file

@ -7,10 +7,9 @@
//
#pragma once
#include <QtCore/QLoggingCategory>
#include <OVR_CAPI_GL.h>
#include <ovr_capi.h>
#include <GLMHelpers.h>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_transform.hpp>
@ -18,106 +17,107 @@
#include <controllers/Forward.h>
Q_DECLARE_LOGGING_CATEGORY(displayplugins)
Q_DECLARE_LOGGING_CATEGORY(oculus)
Q_DECLARE_LOGGING_CATEGORY(oculusLog)
void logWarning(const char* what);
void logCritical(const char* what);
bool oculusAvailable();
ovrSession acquireOculusSession();
void releaseOculusSession();
namespace hifi {
struct ovr {
static bool available();
static ovrSession acquireRenderSession();
static void releaseRenderSession(ovrSession session);
static void withSession(const std::function<void(ovrSession)>& f);
static ovrSessionStatus getStatus();
static ovrTrackingState getTrackingState();
static QString getError();
void handleOVREvents();
bool quitRequested();
bool reorientRequested();
static inline bool quitRequested() { return quitRequested(getStatus()); }
static inline bool reorientRequested() { return reorientRequested(getStatus()); }
static inline bool hmdMounted() { return hmdMounted(getStatus()); }
static inline bool hasInputFocus() { return hasInputFocus(getStatus()); }
// Convenience method for looping over each eye with a lambda
template <typename Function>
inline void ovr_for_each_eye(Function function) {
for (ovrEyeType eye = ovrEyeType::ovrEye_Left;
eye < ovrEyeType::ovrEye_Count;
eye = static_cast<ovrEyeType>(eye + 1)) {
function(eye);
static inline bool quitRequested(const ovrSessionStatus& status) { return status.ShouldQuit != ovrFalse; }
static inline bool reorientRequested(const ovrSessionStatus& status) { return status.ShouldRecenter != ovrFalse; }
static inline bool hmdMounted(const ovrSessionStatus& status) { return status.HmdMounted != ovrFalse; }
static inline bool hasInputFocus(const ovrSessionStatus& status) { return status.HasInputFocus != ovrFalse; }
// Convenience method for looping over each eye with a lambda
static inline void for_each_eye(const std::function<void(ovrEyeType eye)>& f) {
for (ovrEyeType eye = ovrEye_Left; eye < ovrEye_Count; eye = static_cast<ovrEyeType>(eye + 1)) {
f(eye);
}
}
}
template <typename Function>
inline void ovr_for_each_hand(Function function) {
for (ovrHandType hand = ovrHandType::ovrHand_Left;
hand <= ovrHandType::ovrHand_Right;
hand = static_cast<ovrHandType>(hand + 1)) {
function(hand);
static inline void for_each_hand(const std::function<void(ovrHandType eye)>& f) {
for (ovrHandType hand = ovrHand_Left; hand < ovrHand_Count; hand = static_cast<ovrHandType>(hand + 1)) {
f(hand);
}
}
}
static inline glm::mat4 toGlm(const ovrMatrix4f& om) {
return glm::transpose(glm::make_mat4(&om.M[0][0]));
}
static inline glm::mat4 toGlm(const ovrFovPort& fovport, float nearPlane = 0.01f, float farPlane = 10000.0f) {
return toGlm(ovrMatrix4f_Projection(fovport, nearPlane, farPlane, true));
}
static inline glm::vec3 toGlm(const ovrVector3f& ov) {
return glm::make_vec3(&ov.x);
}
static inline glm::vec2 toGlm(const ovrVector2f& ov) {
return glm::make_vec2(&ov.x);
}
inline glm::mat4 toGlm(const ovrMatrix4f & om) {
return glm::transpose(glm::make_mat4(&om.M[0][0]));
}
static inline glm::uvec2 toGlm(const ovrSizei& ov) {
return glm::uvec2(ov.w, ov.h);
}
inline glm::mat4 toGlm(const ovrFovPort & fovport, float nearPlane = 0.01f, float farPlane = 10000.0f) {
return toGlm(ovrMatrix4f_Projection(fovport, nearPlane, farPlane, true));
}
static inline glm::quat toGlm(const ovrQuatf& oq) {
return glm::make_quat(&oq.x);
}
inline glm::vec3 toGlm(const ovrVector3f & ov) {
return glm::make_vec3(&ov.x);
}
static inline glm::mat4 toGlm(const ovrPosef& op) {
glm::mat4 orientation = glm::mat4_cast(toGlm(op.Orientation));
glm::mat4 translation = glm::translate(glm::mat4(), toGlm(op.Position));
return translation * orientation;
}
inline glm::vec2 toGlm(const ovrVector2f & ov) {
return glm::make_vec2(&ov.x);
}
static inline ovrMatrix4f fromGlm(const glm::mat4& m) {
ovrMatrix4f result;
glm::mat4 transposed(glm::transpose(m));
memcpy(result.M, &(transposed[0][0]), sizeof(float) * 16);
return result;
}
inline glm::uvec2 toGlm(const ovrSizei & ov) {
return glm::uvec2(ov.w, ov.h);
}
static inline ovrVector3f fromGlm(const glm::vec3& v) {
return { v.x, v.y, v.z };
}
inline glm::quat toGlm(const ovrQuatf & oq) {
return glm::make_quat(&oq.x);
}
static inline ovrVector2f fromGlm(const glm::vec2& v) {
return { v.x, v.y };
}
inline glm::mat4 toGlm(const ovrPosef & op) {
glm::mat4 orientation = glm::mat4_cast(toGlm(op.Orientation));
glm::mat4 translation = glm::translate(glm::mat4(), toGlm(op.Position));
return translation * orientation;
}
static inline ovrSizei fromGlm(const glm::uvec2& v) {
return { (int)v.x, (int)v.y };
}
inline ovrMatrix4f ovrFromGlm(const glm::mat4 & m) {
ovrMatrix4f result;
glm::mat4 transposed(glm::transpose(m));
memcpy(result.M, &(transposed[0][0]), sizeof(float) * 16);
return result;
}
static inline ovrQuatf fromGlm(const glm::quat& q) {
return { q.x, q.y, q.z, q.w };
}
inline ovrVector3f ovrFromGlm(const glm::vec3 & v) {
return{ v.x, v.y, v.z };
}
static inline ovrPosef poseFromGlm(const glm::mat4& m) {
glm::vec3 translation = glm::vec3(m[3]) / m[3].w;
glm::quat orientation = glm::quat_cast(m);
ovrPosef result;
result.Orientation = fromGlm(orientation);
result.Position = fromGlm(translation);
return result;
}
inline ovrVector2f ovrFromGlm(const glm::vec2 & v) {
return{ v.x, v.y };
}
static controller::Pose toControllerPose(ovrHandType hand, const ovrPoseStatef& handPose);
static controller::Pose toControllerPose(ovrHandType hand, const ovrPoseStatef& handPose, const ovrPoseStatef& lastHandPose);
inline ovrSizei ovrFromGlm(const glm::uvec2 & v) {
return{ (int)v.x, (int)v.y };
}
};
inline ovrQuatf ovrFromGlm(const glm::quat & q) {
return{ q.x, q.y, q.z, q.w };
}
inline ovrPosef ovrPoseFromGlm(const glm::mat4 & m) {
glm::vec3 translation = glm::vec3(m[3]) / m[3].w;
glm::quat orientation = glm::quat_cast(m);
ovrPosef result;
result.Orientation = ovrFromGlm(orientation);
result.Position = ovrFromGlm(translation);
return result;
}
controller::Pose ovrControllerPoseToHandPose(
ovrHandType hand,
const ovrPoseStatef& handPose);
controller::Pose ovrControllerRotationToHandRotation(ovrHandType hand,
const ovrPoseStatef& handPose, const ovrPoseStatef& lastHandPose);
} // namespace hifi

View file

@ -12,6 +12,7 @@
//
var DEFAULT_SCRIPTS_COMBINED = [
"system/request-service.js",
"system/progress.js",
"system/away.js",
"system/audio.js",
@ -32,7 +33,7 @@ var DEFAULT_SCRIPTS_COMBINED = [
"system/emote.js"
];
var DEFAULT_SCRIPTS_SEPARATE = [
"system/controllers/controllerScripts.js"
"system/controllers/controllerScripts.js",
//"system/chat.js"
];

View file

@ -1119,7 +1119,7 @@ function startRadar() {
function endRadar() {
printd("-- endRadar");
Camera.mode = "first person";
Camera.mode = "third person";
radar = false;
Controller.setVPadEnabled(true);

View file

@ -511,6 +511,9 @@
case 'wallet_availableUpdatesReceived':
// NOP
break;
case 'http.request':
// Handled elsewhere, don't log.
break;
default:
print('Unrecognized message from QML:', JSON.stringify(message));
}

View file

@ -273,7 +273,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
this.shouldSendStart = false;
this.equipedWithSecondary = false;
this.handHasBeenRightsideUp = false;
this.mouseEquip = false;
this.parameters = makeDispatcherModuleParameters(
300,
@ -283,11 +282,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
var equipHotspotBuddy = new EquipHotspotBuddy();
this.setMessageGrabData = function(entityProperties, mouseEquip) {
this.setMessageGrabData = function(entityProperties) {
if (entityProperties) {
this.messageGrabEntity = true;
this.grabEntityProps = entityProperties;
this.mouseEquip = mouseEquip;
}
};
@ -584,7 +582,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
this.targetEntityID = null;
this.messageGrabEntity = false;
this.grabEntityProps = null;
this.mouseEquip = false;
};
this.updateInputs = function (controllerData) {
@ -630,14 +627,12 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
// if the potentialHotspot is cloneable, clone it and return it
// if the potentialHotspot os not cloneable and locked return null
if (potentialEquipHotspot &&
(((this.triggerSmoothedSqueezed() || this.secondarySmoothedSqueezed()) && !this.waitForTriggerRelease) ||
this.messageGrabEntity)) {
this.grabbedHotspot = potentialEquipHotspot;
this.targetEntityID = this.grabbedHotspot.entityID;
this.startEquipEntity(controllerData);
this.messageGrabEntity = false;
this.equipedWithSecondary = this.secondarySmoothedSqueezed();
return makeRunningValues(true, [potentialEquipHotspot.entityID], []);
} else {
@ -661,7 +656,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
var timestamp = Date.now();
this.updateInputs(controllerData);
if (!this.mouseEquip && !this.isTargetIDValid(controllerData)) {
if (!this.messageGrabEntity && !this.isTargetIDValid(controllerData)) {
this.endEquipEntity();
return makeRunningValues(false, [], []);
}
@ -762,9 +757,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
var equipModule = (data.hand === "left") ? leftEquipEntity : rightEquipEntity;
var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES);
entityProperties.id = data.entityID;
var mouseEquip = false;
equipModule.setMessageGrabData(entityProperties, mouseEquip);
equipModule.setMessageGrabData(entityProperties);
} catch (e) {
print("WARNING: equipEntity.js -- error parsing Hifi-Hand-Grab message: " + message);
}
@ -812,15 +805,14 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
var distanceToLeftHand = Vec3.distance(entityProperties.position, leftHandPosition);
var leftHandAvailable = leftEquipEntity.targetEntityID === null;
var rightHandAvailable = rightEquipEntity.targetEntityID === null;
var mouseEquip = true;
if (rightHandAvailable && (distanceToRightHand < distanceToLeftHand || !leftHandAvailable)) {
// clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags)
clearGrabActions(entityID);
rightEquipEntity.setMessageGrabData(entityProperties, mouseEquip);
rightEquipEntity.setMessageGrabData(entityProperties);
} else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) {
// clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags)
clearGrabActions(entityID);
leftEquipEntity.setMessageGrabData(entityProperties, mouseEquip);
leftEquipEntity.setMessageGrabData(entityProperties);
}
}
}

View file

@ -282,7 +282,7 @@ Script.include("/~/system/libraries/Xform.js");
this.previousRoomControllerPosition = roomControllerPosition;
};
this.endNearGrabAction = function () {
this.endFarGrabAction = function () {
ensureDynamic(this.grabbedThingID);
this.distanceHolding = false;
this.distanceRotating = false;
@ -402,7 +402,7 @@ Script.include("/~/system/libraries/Xform.js");
this.run = function (controllerData) {
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE ||
this.notPointingAtEntity(controllerData) || this.targetIsNull()) {
this.endNearGrabAction();
this.endFarGrabAction();
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
this.highlightedEntity);
this.highlightedEntity = null;
@ -430,11 +430,12 @@ Script.include("/~/system/libraries/Xform.js");
}
if (this.actionID) {
// if we are doing a distance grab and the object gets close enough to the controller,
// if we are doing a distance grab and the object or tablet gets close enough to the controller,
// stop the far-grab so the near-grab or equip can take over.
for (var k = 0; k < nearGrabReadiness.length; k++) {
if (nearGrabReadiness[k].active && nearGrabReadiness[k].targets[0] === this.grabbedThingID) {
this.endNearGrabAction();
if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.grabbedThingID
|| HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) {
this.endFarGrabAction();
return makeRunningValues(false, [], []);
}
}
@ -445,7 +446,7 @@ Script.include("/~/system/libraries/Xform.js");
// where it could near-grab something, stop searching.
for (var j = 0; j < nearGrabReadiness.length; j++) {
if (nearGrabReadiness[j].active) {
this.endNearGrabAction();
this.endFarGrabAction();
return makeRunningValues(false, [], []);
}
}
@ -577,7 +578,7 @@ Script.include("/~/system/libraries/Xform.js");
var disableModule = getEnabledModuleByName(moduleName);
if (disableModule) {
if (disableModule.disableModules) {
this.endNearGrabAction();
this.endFarGrabAction();
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
this.highlightedEntity);
this.highlightedEntity = null;

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