diff --git a/android/app/build.gradle b/android/app/build.gradle index 46de9642d9..699008092c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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') } - diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java index e05b25f3c3..b98849d051 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java @@ -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; diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java index ea03864695..ca5e0c17bd 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java @@ -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 taggedStories = new ArrayList<>(); + Set 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 restOfPagesCallback, Callback firstPageCallback) { + private void getUserStoryPage(int pageNumber, List userStoriesList, String tagsFilter, Callback restOfPagesCallback) { Call 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() { @Override public void onResponse(Call call, Response 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 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 diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java index 97558d2681..4f8b33b481 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java @@ -54,27 +54,10 @@ public class DomainAdapter extends RecyclerView.Adapter 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 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 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); diff --git a/android/build.gradle b/android/build.gradle index f09dc97850..f1fe4ffc7f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -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 diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 36e5a065df..029c829022 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -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}") diff --git a/cmake/templates/BuildInfo.h.in b/cmake/templates/BuildInfo.h.in index 904d17293b..9fc9d9be81 100644 --- a/cmake/templates/BuildInfo.h.in +++ b/cmake/templates/BuildInfo.h.in @@ -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 + +} diff --git a/cmake/templates/console-build-info.json.in b/cmake/templates/console-build-info.json.in index c1ef010e08..6b4ee99292 100644 --- a/cmake/templates/console-build-info.json.in +++ b/cmake/templates/console-build-info.json.in @@ -1,4 +1,5 @@ { "releaseType": "@RELEASE_TYPE@", - "buildIdentifier": "@BUILD_VERSION@" + "buildIdentifier": "@BUILD_VERSION@", + "organization": "@BUILD_ORGANIZATION@" } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 42a74aa2fc..e69e241182 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -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; @@ -1122,7 +1122,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 +2684,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; } diff --git a/interface/resources/meshes/defaultAvatar_full.fst b/interface/resources/meshes/defaultAvatar_full.fst index aa1c17fc40..b47faeb8a8 100644 --- a/interface/resources/meshes/defaultAvatar_full.fst +++ b/interface/resources/meshes/defaultAvatar_full.fst @@ -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 diff --git a/interface/resources/meshes/mannequin/Eyes.png b/interface/resources/meshes/mannequin/Eyes.png new file mode 100644 index 0000000000..df35dc9fee Binary files /dev/null and b/interface/resources/meshes/mannequin/Eyes.png differ diff --git a/interface/resources/meshes/mannequin/Eyes.ktx b/interface/resources/meshes/mannequin/Eyes_bcn.ktx similarity index 99% rename from interface/resources/meshes/mannequin/Eyes.ktx rename to interface/resources/meshes/mannequin/Eyes_bcn.ktx index ada45776a0..934cba3180 100644 Binary files a/interface/resources/meshes/mannequin/Eyes.ktx and b/interface/resources/meshes/mannequin/Eyes_bcn.ktx differ diff --git a/interface/resources/meshes/mannequin/StingrayPBS10_Base_Color.png b/interface/resources/meshes/mannequin/StingrayPBS10_Base_Color.png new file mode 100644 index 0000000000..941955916a Binary files /dev/null and b/interface/resources/meshes/mannequin/StingrayPBS10_Base_Color.png differ diff --git a/interface/resources/meshes/mannequin/StingrayPBS10_Base_Color_bcn.ktx b/interface/resources/meshes/mannequin/StingrayPBS10_Base_Color_bcn.ktx new file mode 100644 index 0000000000..cbb6cfa992 Binary files /dev/null and b/interface/resources/meshes/mannequin/StingrayPBS10_Base_Color_bcn.ktx differ diff --git a/interface/resources/meshes/mannequin/StingrayPBS10_Normal_OpenGL.png b/interface/resources/meshes/mannequin/StingrayPBS10_Normal_OpenGL.png new file mode 100644 index 0000000000..fc86048656 Binary files /dev/null and b/interface/resources/meshes/mannequin/StingrayPBS10_Normal_OpenGL.png differ diff --git a/interface/resources/meshes/mannequin/StingrayPBS10_Normal_OpenGL_bcn.ktx b/interface/resources/meshes/mannequin/StingrayPBS10_Normal_OpenGL_bcn.ktx new file mode 100644 index 0000000000..aad0b108b3 Binary files /dev/null and b/interface/resources/meshes/mannequin/StingrayPBS10_Normal_OpenGL_bcn.ktx differ diff --git a/interface/resources/meshes/mannequin/lambert1_Base_Color.ktx b/interface/resources/meshes/mannequin/lambert1_Base_Color.ktx deleted file mode 100644 index f151352592..0000000000 Binary files a/interface/resources/meshes/mannequin/lambert1_Base_Color.ktx and /dev/null differ diff --git a/interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx b/interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx deleted file mode 100644 index fb738063ae..0000000000 Binary files a/interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx and /dev/null differ diff --git a/interface/resources/meshes/mannequin/lambert1_Roughness.png b/interface/resources/meshes/mannequin/lambert1_Roughness.png new file mode 100644 index 0000000000..9efa372541 Binary files /dev/null and b/interface/resources/meshes/mannequin/lambert1_Roughness.png differ diff --git a/interface/resources/meshes/mannequin/lambert1_Roughness.ktx b/interface/resources/meshes/mannequin/lambert1_Roughness_bcn.ktx similarity index 99% rename from interface/resources/meshes/mannequin/lambert1_Roughness.ktx rename to interface/resources/meshes/mannequin/lambert1_Roughness_bcn.ktx index afe96d1631..628e6ce99b 100644 Binary files a/interface/resources/meshes/mannequin/lambert1_Roughness.ktx and b/interface/resources/meshes/mannequin/lambert1_Roughness_bcn.ktx differ diff --git a/interface/resources/meshes/mannequin/mannequin.baked.fbx b/interface/resources/meshes/mannequin/mannequin.baked.fbx index 2b0e1cf04b..63611b0b6c 100644 Binary files a/interface/resources/meshes/mannequin/mannequin.baked.fbx and b/interface/resources/meshes/mannequin/mannequin.baked.fbx differ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2c0b05d2d9..c4e12281f2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -697,8 +697,8 @@ private: }; /**jsdoc - *

The Controller.Hardware.Application object has properties representing Interface's state. The property - * values are integer IDs, uniquely identifying each output. Read-only. These can be mapped to actions or functions or + *

The Controller.Hardware.Application object has properties representing Interface's state. The property + * values are integer IDs, uniquely identifying each output. Read-only. These can be mapped to actions or functions or * Controller.Standard items in a {@link RouteObject} mapping (e.g., using the {@link RouteObject#when} method). * Each data value is either 1.0 for "true" or 0.0 for "false".

* @@ -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(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(); 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{ 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(); @@ -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 @@ -5787,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(); @@ -6155,7 +6156,9 @@ void Application::updateWindowTitle() const { auto nodeList = DependencyManager::get(); auto accountManager = DependencyManager::get(); - QString buildVersion = " (build " + applicationVersion() + ")"; + QString buildVersion = " - " + + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build")) + + " " + applicationVersion(); QString loginStatus = accountManager->isLoggedIn() ? "" : " (NOT LOGGED IN)"; @@ -7716,7 +7719,7 @@ void Application::sendLambdaEvent(const std::function& f) { } else { LambdaEvent event(f); QCoreApplication::sendEvent(this, &event); - } + } } void Application::initPlugins(const QStringList& arguments) { @@ -7939,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(); diff --git a/interface/src/Crashpad.cpp b/interface/src/Crashpad.cpp index 45f1d0778f..88651925d5 100644 --- a/interface/src/Crashpad.cpp +++ b/interface/src/Crashpad.cpp @@ -18,6 +18,7 @@ #if HAS_CRASHPAD #include +#include #include #include @@ -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"); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 0e5ba0e4e7..6f4300862d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -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, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 8569911cbd..6fb089acd8 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -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"; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 2e9c9fdecd..4d133706e6 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -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(avatarData); avatar->addToScene(avatar, scene, transaction); } } else { - for (auto avatarData : _avatarHash) { + for (auto avatarData : avatarHashCopy) { auto avatar = std::static_pointer_cast(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(avatarData); if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) || (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 20032cfaea..6db04f72bd 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -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)); @@ -2830,6 +2850,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); @@ -2852,6 +2873,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; @@ -2861,6 +2884,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(); } @@ -3029,9 +3248,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 { @@ -3102,11 +3319,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); @@ -3502,6 +3727,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(getHead()); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 05d70ee54f..23119f49af 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -105,6 +105,9 @@ class MyAvatar : public Avatar { * by 30cm. Read-only. * @property {Pose} rightHandTipPose - The pose of the right hand as determined by the hand controllers, with the position * by 30cm. Read-only. + * @property {boolean} centerOfGravityModelEnabled=true - If true 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 false + * will result in the default behaviour where the hips are placed under the head. * @property {boolean} hmdLeanRecenterEnabled=true - If true 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 false 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 @@ -1109,7 +1137,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 @@ -1460,8 +1497,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. @@ -1497,9 +1534,12 @@ private: void setForceActivateVertical(bool val); bool getForceActivateHorizontal() const; void setForceActivateHorizontal(bool val); - std::atomic _forceActivateRotation{ false }; - std::atomic _forceActivateVertical{ false }; - std::atomic _forceActivateHorizontal{ false }; + bool getToggleHipsFollowing() const; + void setToggleHipsFollowing(bool followHead); + std::atomic _forceActivateRotation { false }; + std::atomic _forceActivateVertical { false }; + std::atomic _forceActivateHorizontal { false }; + std::atomic _toggleHipsFollowing { true }; }; FollowHelper _follow; @@ -1512,6 +1552,7 @@ private: bool _prevShouldDrawHead; bool _rigEnabled { true }; + bool _enableDebugDrawBaseOfSupport { false }; bool _enableDebugDrawDefaultPose { false }; bool _enableDebugDrawAnimPose { false }; bool _enableDebugDrawHandControllers { false }; @@ -1534,6 +1575,7 @@ private: std::map _controllerPoseMap; mutable std::mutex _controllerPoseMapMutex; + bool _centerOfGravityModelEnabled { true }; bool _hmdLeanRecenterEnabled { true }; bool _sprint { false }; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index f317f6b2c1..c15b00ca19 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -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; diff --git a/interface/src/avatar/MySkeletonModel.h b/interface/src/avatar/MySkeletonModel.h index 252b6c293b..ebef9796a4 100644 --- a/interface/src/avatar/MySkeletonModel.h +++ b/interface/src/avatar/MySkeletonModel.h @@ -28,6 +28,8 @@ private: AnimPose _prevHips; // sensor frame bool _prevHipsValid { false }; + bool _prevIsFlying { false }; + float _flyIdleTimer { 0.0f }; std::map _jointRotationFrameOffsetMap; }; diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index f7e749317d..e003ae88a0 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -536,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()->setWalletStatus((uint)WalletStatus::WALLET_STATUS_READY); return true; } } @@ -615,7 +614,11 @@ void Wallet::updateImageProvider() { SecurityImageProvider* securityImageProvider; // inform offscreenUI security image provider - QQmlEngine* engine = DependencyManager::get()->getSurfaceContext()->engine(); + auto offscreenUI = DependencyManager::get(); + if (!offscreenUI) { + return; + } + QQmlEngine* engine = offscreenUI->getSurfaceContext()->engine(); securityImageProvider = reinterpret_cast(engine->imageProvider(SecurityImageProvider::PROVIDER_NAME)); securityImageProvider->setSecurityImage(_securityImage); diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index fbb5aae84c..c8056c11c8 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -100,24 +100,32 @@ void ModelOverlay::update(float deltatime) { processMaterials(); emit DependencyManager::get()->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(getRenderItemID(), [](Overlay& data) {}); } scene->enqueueTransaction(transaction); diff --git a/libraries/animation/src/AnimUtil.cpp b/libraries/animation/src/AnimUtil.cpp index 65c605b5ba..acb90126fc 100644 --- a/libraries/animation/src/AnimUtil.cpp +++ b/libraries/animation/src/AnimUtil.cpp @@ -9,7 +9,9 @@ // #include "AnimUtil.h" -#include "GLMHelpers.h" +#include +#include +#include // 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); +} diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index f2cceb361b..3cd7f4b6fb 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -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 diff --git a/libraries/auto-updater/src/AutoUpdater.cpp b/libraries/auto-updater/src/AutoUpdater.cpp index e58ac067a6..6749cd9e10 100644 --- a/libraries/auto-updater/src/AutoUpdater.cpp +++ b/libraries/auto-updater/src/AutoUpdater.cpp @@ -11,6 +11,8 @@ #include "AutoUpdater.h" +#include + #include #include #include @@ -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); -} \ No newline at end of file +} diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 829c98a418..974ae92432 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -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); diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 6747025de0..ef6f7845eb 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -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: diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index a48a25b0b2..f1748ca069 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -206,7 +206,7 @@ private: render::ItemKey _itemKey { render::ItemKey::Builder().withTypeMeta() }; - bool _didLastVisualGeometryRequestSucceed { false }; + bool _didLastVisualGeometryRequestSucceed { true }; void processMaterials(); }; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 18e60ef5ef..0b1334daba 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -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) { diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index b69733c18d..253a17c6f9 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -40,7 +40,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::FBXReaderNodeReparenting); + return static_cast(AvatarMixerPacketVersion::FixMannequinDefaultAvatarFeet); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); // ICE packets diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 5203a9d178..9faa92725f 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -282,7 +282,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarIdentityLookAtSnapping, UpdatedMannequinDefaultAvatar, AvatarJointDefaultPoseFlags, - FBXReaderNodeReparenting + FBXReaderNodeReparenting, + FixMannequinDefaultAvatarFeet }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index 122fc16e61..82426a3a1f 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -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", cullFunctor, tagBits, tagMask); + if (isDeferred) { + task.addJob("RenderShadowTask", cullFunctor, tagBits, tagMask); + } const auto items = task.addJob("FetchCullSort", cullFunctor, tagBits, tagMask); assert(items.canCast()); diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 930da6a494..e90e25d5b0 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -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 }; diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 75446754d5..4be8ad0e41 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -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) { diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 0e1af27cd2..7e6ef4cb28 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -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); } diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 045dff1295..06ce05e721 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,7 +32,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/emote.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ - "system/controllers/controllerScripts.js" + "system/controllers/controllerScripts.js", //"system/chat.js" ]; diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js index 5d93ed4db1..1cbe721ad0 100644 --- a/scripts/system/+android/radar.js +++ b/scripts/system/+android/radar.js @@ -1119,7 +1119,7 @@ function startRadar() { function endRadar() { printd("-- endRadar"); - Camera.mode = "first person"; + Camera.mode = "third person"; radar = false; Controller.setVPadEnabled(true); diff --git a/server-console/src/main.js b/server-console/src/main.js index b08db6222f..8a92fc8a5d 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -76,10 +76,7 @@ function getBuildInfo() { const buildInfo = getBuildInfo(); function getRootHifiDataDirectory() { - var organization = "High Fidelity"; - if (buildInfo.releaseType != "PRODUCTION") { - organization += ' - ' + buildInfo.buildIdentifier; - } + var organization = buildInfo.organization; if (osType == 'Windows_NT') { return path.resolve(osHomeDir(), 'AppData/Roaming', organization); } else if (osType == 'Darwin') {