mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-07 20:22:27 +02:00
Merge branch 'master' into snoretoast
Two simple conflicts fixed.
This commit is contained in:
commit
01ef636411
379 changed files with 8364 additions and 4334 deletions
14
BUILD_WIN.md
14
BUILD_WIN.md
|
@ -9,15 +9,17 @@ Note: The prerequisites will require about 10 GB of space on your drive. You wil
|
|||
|
||||
If you don’t have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/).
|
||||
|
||||
When selecting components, check "Desktop development with C++." Also check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)" on the Summary toolbar on the right.
|
||||
When selecting components, check "Desktop development with C++." Also on the right on the Summary toolbar, check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)".
|
||||
|
||||
### Step 2. Installing CMake
|
||||
|
||||
Download and install the latest version of CMake 3.9. Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). Make sure to check "Add CMake to system PATH for all users" when prompted during installation.
|
||||
Download and install the latest version of CMake 3.9.
|
||||
|
||||
Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.9 Version page](https://cmake.org/files/v3.9/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted.
|
||||
|
||||
### Step 3. Installing Qt
|
||||
|
||||
Download and install the [Qt Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.10.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)".
|
||||
Download and install the [Qt Open Source Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.10.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)".
|
||||
|
||||
Note: Installing the Sources is optional but recommended if you have room for them (~2GB).
|
||||
|
||||
|
@ -56,9 +58,9 @@ Where `%HIFI_DIR%` is the directory for the highfidelity repository.
|
|||
|
||||
Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio.
|
||||
|
||||
Change the Solution Configuration (next to the green play button) from "Debug" to "Release" for best performance.
|
||||
Change the Solution Configuration (menu ribbon under the menu bar, next to the green play button) from "Debug" to "Release" for best performance.
|
||||
|
||||
Run `Build > Build Solution`.
|
||||
Run from the menu bar `Build > Build Solution`.
|
||||
|
||||
### Step 9. Testing Interface
|
||||
|
||||
|
@ -66,7 +68,7 @@ Create another environment variable (see Step #4)
|
|||
* Set "Variable name": `_NO_DEBUG_HEAP`
|
||||
* Set "Variable value": `1`
|
||||
|
||||
In Visual Studio, right+click "interface" under the Apps folder in Solution Explorer and select "Set as Startup Project". Run `Debug > Start Debugging`.
|
||||
In Visual Studio, right+click "interface" under the Apps folder in Solution Explorer and select "Set as Startup Project". Run from the menu bar `Debug > Start Debugging`.
|
||||
|
||||
Now, you should have a full build of High Fidelity and be able to run the Interface using Visual Studio. Please check our [Docs](https://wiki.highfidelity.com/wiki/Main_Page) for more information regarding the programming workflow.
|
||||
|
||||
|
|
|
@ -22,11 +22,19 @@ 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'
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null
|
||||
storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : ''
|
||||
keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : ''
|
||||
keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
@ -38,6 +46,10 @@ android {
|
|||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig project.hasProperty("HIFI_ANDROID_KEYSTORE") &&
|
||||
project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") &&
|
||||
project.hasProperty("HIFI_ANDROID_KEY_ALIAS") &&
|
||||
project.hasProperty("HIFI_ANDROID_KEY_PASSWORD")? signingConfigs.release : null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,4 +128,3 @@ dependencies {
|
|||
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
}
|
||||
|
||||
|
|
|
@ -49,12 +49,6 @@
|
|||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
>
|
||||
|
||||
<intent-filter>
|
||||
<category android:name="com.google.intent.category.DAYDREAM"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="native-lib"/>
|
||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
|
||||
|
|
|
@ -14,12 +14,16 @@ package io.highfidelity.hifiinterface;
|
|||
import android.content.Intent;
|
||||
import android.content.res.AssetManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Vibrator;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.WindowManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.qtproject.qt5.android.QtLayout;
|
||||
import org.qtproject.qt5.android.QtSurface;
|
||||
import org.qtproject.qt5.android.bindings.QtActivity;
|
||||
|
||||
/*import com.google.vr.cardboard.DisplaySynchronizer;
|
||||
|
@ -31,6 +35,9 @@ import android.content.pm.ActivityInfo;
|
|||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class InterfaceActivity extends QtActivity {
|
||||
|
||||
|
@ -134,6 +141,7 @@ public class InterfaceActivity extends QtActivity {
|
|||
protected void onResume() {
|
||||
super.onResume();
|
||||
nativeEnterForeground();
|
||||
surfacesWorkaround();
|
||||
//gvrApi.resumeTracking();
|
||||
}
|
||||
|
||||
|
@ -158,6 +166,41 @@ public class InterfaceActivity extends QtActivity {
|
|||
Log.w("[VR]", "Portrait detected but not in VR mode. Should not happen");
|
||||
}
|
||||
}
|
||||
surfacesWorkaround();
|
||||
}
|
||||
|
||||
private void surfacesWorkaround() {
|
||||
FrameLayout fl = findViewById(android.R.id.content);
|
||||
if (fl.getChildCount() > 0) {
|
||||
QtLayout qtLayout = (QtLayout) fl.getChildAt(0);
|
||||
if (qtLayout.getChildCount() > 1) {
|
||||
QtSurface s1 = (QtSurface) qtLayout.getChildAt(0);
|
||||
QtSurface s2 = (QtSurface) qtLayout.getChildAt(1);
|
||||
Integer subLayer1 = 0;
|
||||
Integer subLayer2 = 0;
|
||||
try {
|
||||
String field;
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
field = "mSubLayer";
|
||||
} else {
|
||||
field = "mWindowType";
|
||||
}
|
||||
Field f = s1.getClass().getSuperclass().getDeclaredField(field);
|
||||
f.setAccessible(true);
|
||||
subLayer1 = (Integer) f.get(s1);
|
||||
subLayer2 = (Integer) f.get(s2);
|
||||
if (subLayer1 < subLayer2) {
|
||||
s1.setVisibility(View.VISIBLE);
|
||||
s2.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
s1.setVisibility(View.INVISIBLE);
|
||||
s2.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Log.e(TAG, "Workaround failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void openUrlInAndroidWebView(String urlString) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
|
@ -66,19 +66,19 @@ ext {
|
|||
def baseFolder = new File(HIFI_ANDROID_PRECOMPILED)
|
||||
def appDir = new File(projectDir, 'app')
|
||||
def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a')
|
||||
def baseUrl = 'https://hifi-public.s3.amazonaws.com/austin/android/'
|
||||
def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/'
|
||||
|
||||
def qtFile='qt-5.9.3_linux_armv8-libcpp_openssl.tgz'
|
||||
def qtChecksum='04599670ccca84bd2b15f6915568eb2d'
|
||||
def qtVersionId='PeoqzN31n.YvLfs9JE2SgHgZ4.IaKAlt'
|
||||
def qtVersionId='8QbCma4ryEPgBYn_8kgYgB10IvNx9I1W'
|
||||
if (Os.isFamily(Os.FAMILY_MAC)) {
|
||||
qtFile = 'qt-5.9.3_osx_armv8-libcpp_openssl.tgz'
|
||||
qtChecksum='4b02de9d67d6bfb202355a808d2d9c59'
|
||||
qtVersionId='HygCmtMLPYioyil0DfXckGVzhw2SXZA9'
|
||||
qtVersionId='2gfgoYCggJGyXxKiazaPGsMs1Gn9j4og'
|
||||
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
qtFile = 'qt-5.9.3_win_armv8-libcpp_openssl.tgz'
|
||||
qtChecksum='c3e25db64002d0f43cf565e0ef708911'
|
||||
qtVersionId='HeVObSVLCBoc7yY7He1oBMvPIH0VkClT'
|
||||
qtVersionId='xKIteC6HO0xrmcWeMmhQcmKyPEsnUrcZ'
|
||||
}
|
||||
|
||||
def packages = [
|
||||
|
@ -88,62 +88,67 @@ def packages = [
|
|||
checksum: qtChecksum,
|
||||
],
|
||||
bullet: [
|
||||
file: 'bullet-2.83_armv8-libcpp.tgz',
|
||||
versionId: 'ljb7v.1IjVRqyopUKVDbVnLA4z88J8Eo',
|
||||
checksum: '2c558d604fce337f5eba3eb7ec1252fd',
|
||||
file: 'bullet-2.88_armv8-libcpp.tgz',
|
||||
versionId: 'S8YaoED0Cl8sSb8fSV7Q2G1lQJSNDxqg',
|
||||
checksum: '81642779ccb110f8c7338e8739ac38a0',
|
||||
],
|
||||
draco: [
|
||||
file: 'draco_armv8-libcpp.tgz',
|
||||
versionId: 'cA3tVJSmkvb1naA3l6D_Jv2Noh.4yc4m',
|
||||
versionId: '3.B.uBj31kWlgND3_R2xwQzT_TP6Dz_8',
|
||||
checksum: '617a80d213a5ec69fbfa21a1f2f738cd',
|
||||
],
|
||||
glad: [
|
||||
file: 'glad_armv8-libcpp.zip',
|
||||
versionId: 'Q9szthzeye8fFyAA.cY26Lgn2B8kezEE',
|
||||
versionId: 'r5Zran.JSCtvrrB6Q4KaqfIoALPw3lYY',
|
||||
checksum: 'a8ee8584cf1ccd34766c7ddd9d5e5449',
|
||||
],
|
||||
glm: [
|
||||
file: 'glm-0.9.8.tgz',
|
||||
versionId: 'BlkJNwaYV2Gfy5XwMeU7K0uzPDRKFMt2',
|
||||
checksum: 'd2b42cee31d2bc17bab6ce69e6b3f30a',
|
||||
file: 'glm-0.9.8.5-patched.tgz',
|
||||
versionId: 'cskfMoJrFlAeqI3WPxemyO_Cxt7rT9EJ',
|
||||
checksum: '067b5fe16b220b5b1a1039ba51b062ae',
|
||||
],
|
||||
gvr: [
|
||||
file: 'gvrsdk_v1.101.0.tgz',
|
||||
versionId: 'UTberAIFraEfF9IVjoV66u1DTPTopgeY',
|
||||
versionId: 'nqBV_j81Uc31rC7bKIrlya_Hah4v3y5r',
|
||||
checksum: '57fd02baa069176ba18597a29b6b4fc7',
|
||||
],
|
||||
nvtt: [
|
||||
file: 'nvtt_armv8-libcpp.zip',
|
||||
versionId: 'vLqrqThvpq4gp75BHMAqO6HhfTXaa0An',
|
||||
versionId: 'lmkBVR5t4UF1UUwMwEirnk9H_8Nt90IO',
|
||||
checksum: 'eb46d0b683e66987190ed124aabf8910',
|
||||
sharedLibFolder: 'lib',
|
||||
includeLibs: ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so'],
|
||||
],
|
||||
openssl: [
|
||||
file: 'openssl-1.1.0g_armv8.tgz',
|
||||
versionId: 'DmahmSGFS4ltpHyTdyQvv35WOeUOiib9',
|
||||
versionId: 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW',
|
||||
checksum: 'cabb681fbccd79594f65fcc266e02f32',
|
||||
],
|
||||
polyvox: [
|
||||
file: 'polyvox_armv8-libcpp.tgz',
|
||||
versionId: 'LDJtzMTvdm4SAc2KYg8Cg6uwWk4Vq3e3',
|
||||
checksum: '349ad5b72aaf2749ca95d847e60c5314',
|
||||
versionId: 'A2kbKiNhpIenGq23bKRRzg7IMAI5BI92',
|
||||
checksum: 'dba88b3a098747af4bb169e9eb9af57e',
|
||||
sharedLibFolder: 'lib',
|
||||
includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'],
|
||||
],
|
||||
tbb: [
|
||||
file: 'tbb-2018_U1_armv8_libcpp.tgz',
|
||||
versionId: 'YZliDD8.Menh1IVXKEuLPeO3xAjJ1UdF',
|
||||
versionId: 'mrRbWnv4O4evcM1quRH43RJqimlRtaKB',
|
||||
checksum: '20768f298f53b195e71b414b0ae240c4',
|
||||
sharedLibFolder: 'lib/release',
|
||||
includeLibs: ['libtbb.so', 'libtbbmalloc.so'],
|
||||
],
|
||||
hifiAC: [
|
||||
file: 'libplugins_libhifiCodec.zip',
|
||||
versionId: 'mzKhsRCgVmloqq5bvE.0IwYK1NjGQc_G',
|
||||
versionId: 'i31pW.qNbvFOXRxbyiJUxg3sphaFNmZU',
|
||||
checksum: '9412a8e12c88a4096c1fc843bb9fe52d',
|
||||
sharedLibFolder: '',
|
||||
includeLibs: ['libplugins_libhifiCodec.so']
|
||||
],
|
||||
etc2comp: [
|
||||
file: 'etc2comp-patched-armv8-libcpp.tgz',
|
||||
versionId: 'bHhGECRAQR1vkpshBcK6ByNc1BQIM8gU',
|
||||
checksum: '14b02795d774457a33bbc60e00a786bc'
|
||||
]
|
||||
]
|
||||
|
||||
|
@ -152,15 +157,15 @@ def scribeLocalFile='scribe' + EXEC_SUFFIX
|
|||
|
||||
def scribeFile='scribe_linux_x86_64'
|
||||
def scribeChecksum='ca4b904f52f4f993c29175ba96798fa6'
|
||||
def scribeVersion='wgpf4dB2Ltzg4Lb2jJ4nPFsHoDkmK_OO'
|
||||
def scribeVersion='u_iTrJDaE95i2abTPXOpPZckGBIim53G'
|
||||
if (Os.isFamily(Os.FAMILY_MAC)) {
|
||||
scribeFile = 'scribe_osx_x86_64'
|
||||
scribeChecksum='72db9d32d4e1e50add755570ac5eb749'
|
||||
scribeVersion='o_NbPrktzEYtBkQf3Tn7zc1nZWzM52w6'
|
||||
scribeVersion='DAW0DmnjCRib4MD8x93bgc2Z2MpPojZC'
|
||||
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
scribeFile = 'scribe_win32_x86_64.exe'
|
||||
scribeChecksum='678e43d290c90fda670c6fefe038a06d'
|
||||
scribeVersion='GCCJxlmd2irvNOFWfZR0U1UCLHndHQrC'
|
||||
scribeVersion='PuullrA_bPlO9kXZRt8rLe536X1UI.m7'
|
||||
}
|
||||
|
||||
def options = [
|
||||
|
@ -361,6 +366,7 @@ task verifyOpenSSL(type: Verify) { def p = packages['openssl']; src new File(bas
|
|||
task verifyPolyvox(type: Verify) { def p = packages['polyvox']; src new File(baseFolder, p['file']); checksum p['checksum'] }
|
||||
task verifyTBB(type: Verify) { def p = packages['tbb']; src new File(baseFolder, p['file']); checksum p['checksum'] }
|
||||
task verifyHifiAC(type: Verify) { def p = packages['hifiAC']; src new File(baseFolder, p['file']); checksum p['checksum'] }
|
||||
task verifyEtc2Comp(type: Verify) { def p = packages['etc2comp']; src new File(baseFolder, p['file']); checksum p['checksum'] }
|
||||
|
||||
task verifyDependencyDownloads(dependsOn: downloadDependencies) { }
|
||||
verifyDependencyDownloads.dependsOn verifyQt
|
||||
|
@ -371,6 +377,7 @@ verifyDependencyDownloads.dependsOn verifyOpenSSL
|
|||
verifyDependencyDownloads.dependsOn verifyPolyvox
|
||||
verifyDependencyDownloads.dependsOn verifyTBB
|
||||
verifyDependencyDownloads.dependsOn verifyHifiAC
|
||||
verifyDependencyDownloads.dependsOn verifyEtc2Comp
|
||||
|
||||
task extractDependencies(dependsOn: verifyDependencyDownloads) {
|
||||
doLast {
|
||||
|
@ -535,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
|
||||
|
|
|
@ -624,8 +624,8 @@ AudioMixerClientData::IgnoreZone& AudioMixerClientData::IgnoreZoneMemo::get(unsi
|
|||
scale = MIN_IGNORE_BOX_SCALE;
|
||||
}
|
||||
|
||||
// quadruple the scale (this is arbitrary number chosen for comfort)
|
||||
const float IGNORE_BOX_SCALE_FACTOR = 4.0f;
|
||||
// (this is arbitrary number determined empirically for comfort)
|
||||
const float IGNORE_BOX_SCALE_FACTOR = 2.4f;
|
||||
scale *= IGNORE_BOX_SCALE_FACTOR;
|
||||
|
||||
// create the box (we use a box for the zone for convenience)
|
||||
|
|
|
@ -271,8 +271,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) {
|
||||
otherNodeBox.setScaleStayCentered(minBubbleSize);
|
||||
}
|
||||
// Quadruple the scale of both bounding boxes
|
||||
otherNodeBox.embiggen(4.0f);
|
||||
// Change the scale of both bounding boxes
|
||||
// (This is an arbitrary number determined empirically)
|
||||
otherNodeBox.embiggen(2.4f);
|
||||
|
||||
// Perform the collision check between the two bounding boxes
|
||||
if (nodeBox.touches(otherNodeBox)) {
|
||||
|
|
|
@ -44,6 +44,7 @@ EntityServer::EntityServer(ReceivedMessage& message) :
|
|||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
|
||||
PacketType::EntityClone,
|
||||
PacketType::EntityEdit,
|
||||
PacketType::EntityErase,
|
||||
PacketType::EntityPhysics,
|
||||
|
|
|
@ -233,7 +233,6 @@ OctreeServer::OctreeServer(ReceivedMessage& message) :
|
|||
_argc(0),
|
||||
_argv(NULL),
|
||||
_parsedArgV(NULL),
|
||||
_httpManager(NULL),
|
||||
_statusPort(0),
|
||||
_packetsPerClientPerInterval(10),
|
||||
_packetsTotalPerInterval(DEFAULT_PACKETS_PER_INTERVAL),
|
||||
|
@ -285,7 +284,7 @@ void OctreeServer::initHTTPManager(int port) {
|
|||
QString documentRoot = QString("%1/web").arg(PathUtils::getAppDataPath());
|
||||
|
||||
// setup an httpManager with us as the request handler and the parent
|
||||
_httpManager = new HTTPManager(QHostAddress::AnyIPv4, port, documentRoot, this, this);
|
||||
_httpManager.reset(new HTTPManager(QHostAddress::AnyIPv4, port, documentRoot, this));
|
||||
}
|
||||
|
||||
bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
|
||||
|
|
|
@ -183,7 +183,7 @@ protected:
|
|||
|
||||
bool _isShuttingDown = false;
|
||||
|
||||
HTTPManager* _httpManager;
|
||||
std::unique_ptr<HTTPManager> _httpManager;
|
||||
int _statusPort;
|
||||
QString _statusHost;
|
||||
|
||||
|
|
4
cmake/externals/LibOVR/CMakeLists.txt
vendored
4
cmake/externals/LibOVR/CMakeLists.txt
vendored
|
@ -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
|
||||
|
|
2
cmake/externals/LibOVR/LibOVRCMakeLists.txt
vendored
2
cmake/externals/LibOVR/LibOVRCMakeLists.txt
vendored
|
@ -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")
|
||||
|
|
8
cmake/externals/bullet/CMakeLists.txt
vendored
8
cmake/externals/bullet/CMakeLists.txt
vendored
|
@ -17,8 +17,8 @@ include(ExternalProject)
|
|||
if (WIN32)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.83-ccd-and-cmake-fixes.tgz
|
||||
URL_MD5 03051bf112dcc78ddd296f9cab38fd68
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.88.tgz
|
||||
URL_MD5 0a6876607ebe83e227427215f15946fd
|
||||
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_BULLET3=0 -DBUILD_OPENGL3_DEMOS=0 -DBUILD_BULLET2_DEMOS=0 -DBUILD_UNIT_TESTS=0 -DUSE_GLUT=0 -DUSE_DX11=0
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
|
@ -28,8 +28,8 @@ if (WIN32)
|
|||
else ()
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.83-ccd-and-cmake-fixes.tgz
|
||||
URL_MD5 03051bf112dcc78ddd296f9cab38fd68
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.88.tgz
|
||||
URL_MD5 0a6876607ebe83e227427215f15946fd
|
||||
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_BULLET3=0 -DBUILD_OPENGL3_DEMOS=0 -DBUILD_BULLET2_DEMOS=0 -DBUILD_UNIT_TESTS=0 -DUSE_GLUT=0
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
|
|
55
cmake/externals/etc2comp/CMakeLists.txt
vendored
Normal file
55
cmake/externals/etc2comp/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
set(EXTERNAL_NAME etc2comp)
|
||||
|
||||
if (ANDROID)
|
||||
set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19")
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
set(EXTRA_CMAKE_FLAGS -DCMAKE_CXX_FLAGS=-stdlib=libc++ -DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++)
|
||||
endif ()
|
||||
|
||||
include(ExternalProject)
|
||||
# We use a patched version of etc2comp that properly generates all the necessary mips
|
||||
# See https://github.com/google/etc2comp/pull/29
|
||||
# We also use part of https://github.com/google/etc2comp/pull/1, which fixes a bug
|
||||
# that would override CMAKE_CXX_FLAGS
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://hifi-public.s3.amazonaws.com/dependencies/etc2comp-patched.zip
|
||||
URL_MD5 4c96153eb179acbe619e3d99d3330595
|
||||
CMAKE_ARGS ${ANDROID_CMAKE_ARGS} ${EXTRA_CMAKE_FLAGS}
|
||||
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
)
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
if (WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/build/EtcLib/Debug/EtcLib.lib CACHE FILEPATH "Path to Etc2Comp debug library")
|
||||
|
||||
# use generator expression to ensure the correct library is found when building different configurations in VS
|
||||
set(_LIB_FOLDER "$<$<CONFIG:RelWithDebInfo>:build/EtcLib/RelWithDebInfo>")
|
||||
set(_LIB_FOLDER "${_LIB_FOLDER}$<$<CONFIG:MinSizeRel>:build/EtcLib/MinSizeRel>")
|
||||
set(_LIB_FOLDER "${_LIB_FOLDER}$<$<OR:$<CONFIG:Release>,$<CONFIG:Debug>>:build/EtcLib/Release>")
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/${_LIB_FOLDER}/EtcLib.lib CACHE FILEPATH "Path to Etc2Comp release library")
|
||||
elseif (APPLE)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/build/EtcLib/Debug/libEtcLib.a CACHE FILEPATH "Path to EtcLib debug library")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/build/EtcLib/Release/libEtcLib.a CACHE FILEPATH "Path to EtcLib release library")
|
||||
else ()
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE FILEPATH "Path to EtcLib debug library")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/build/EtcLib/libEtcLib.a CACHE FILEPATH "Path to EtcLib release library")
|
||||
endif ()
|
||||
|
||||
set(ETC_INCLUDE_DIR ${SOURCE_DIR}/EtcLib/Etc CACHE FILEPATH "Path to Etc2Comp/Etc include directory")
|
||||
set(ETCCODEC_INCLUDE_DIR ${SOURCE_DIR}/EtcLib/EtcCodec CACHE FILEPATH "Path to Etc2Comp/EtcCodec include directory")
|
||||
# ETC2COMP_INCLUDE_DIRS will be set later by FindEtc2Comp
|
6
cmake/externals/glm/CMakeLists.txt
vendored
6
cmake/externals/glm/CMakeLists.txt
vendored
|
@ -3,8 +3,8 @@ set(EXTERNAL_NAME glm)
|
|||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.8.zip
|
||||
URL_MD5 579ac77a3110befa3244d68c0ceb7281
|
||||
URL https://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.8.5-patched.zip
|
||||
URL_MD5 7d39ecc1cea275427534c3cfd6dd63f0
|
||||
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
|
||||
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> ${EXTERNAL_ARGS}
|
||||
LOG_DOWNLOAD 1
|
||||
|
@ -18,4 +18,4 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
|||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glm include directories")
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glm include directories")
|
||||
|
|
|
@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content)
|
|||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC67-v4.zip
|
||||
URL_MD5 ba32aed18bfeaac4ccaf5ebb8ea3e804
|
||||
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC68-v2.zip
|
||||
URL_MD5 f7d290471baf7f5694c147217b8fc548
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
|
@ -17,15 +17,13 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
set(DEV_BUILD 0)
|
||||
set(BUILD_GLOBAL_SERVICES "DEVELOPMENT")
|
||||
set(USE_STABLE_GLOBAL_SERVICES 0)
|
||||
set(BUILD_NUMBER 0)
|
||||
set(APP_USER_MODEL_ID "com.highfidelity.sandbox-dev")
|
||||
|
||||
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
|
||||
|
@ -47,17 +45,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")
|
||||
|
||||
|
@ -76,6 +74,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)
|
||||
|
@ -129,8 +175,8 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
set(CONSOLE_SHORTCUT_NAME "Sandbox")
|
||||
set(APP_USER_MODEL_ID "com.highfidelity.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}")
|
||||
|
|
|
@ -61,16 +61,21 @@ macro(SETUP_HIFI_TESTCASE)
|
|||
endif()
|
||||
endforeach()
|
||||
|
||||
|
||||
# Find test classes to build into test executables.
|
||||
# Warn about any .cpp files that are *not* test classes (*Test[s].cpp), since those files will not be used.
|
||||
foreach (SRC_FILE ${TEST_PROJ_SRC_FILES})
|
||||
string(REGEX MATCH ".+Tests?\\.cpp$" TEST_CPP_FILE ${SRC_FILE})
|
||||
string(REGEX MATCH ".+\\.cpp$" NON_TEST_CPP_FILE ${SRC_FILE})
|
||||
string(REGEX MATCH ".+\\.qrc$" QRC_FILE ${SRC_FILE})
|
||||
if (TEST_CPP_FILE)
|
||||
list(APPEND TEST_CASE_FILES ${TEST_CPP_FILE})
|
||||
elseif (NON_TEST_CPP_FILE)
|
||||
message(WARNING "ignoring .cpp file (not a test class -- this will not be linked or compiled!): " ${NON_TEST_CPP_FILE})
|
||||
endif ()
|
||||
if (QRC_FILE)
|
||||
list(APPEND EXTRA_FILES ${QRC_FILE})
|
||||
endif()
|
||||
endforeach ()
|
||||
|
||||
if (TEST_CASE_FILES)
|
||||
|
@ -88,7 +93,7 @@ macro(SETUP_HIFI_TESTCASE)
|
|||
# grab the implemenation and header files
|
||||
set(TARGET_SRCS ${TEST_FILE}) # only one source / .cpp file (the test class)
|
||||
|
||||
add_executable(${TARGET_NAME} ${TEST_FILE})
|
||||
add_executable(${TARGET_NAME} ${TEST_FILE} ${EXTRA_FILES})
|
||||
add_test(${TARGET_NAME}-test ${TARGET_NAME})
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES
|
||||
EXCLUDE_FROM_DEFAULT_BUILD TRUE
|
||||
|
|
22
cmake/macros/TargetEtc2Comp.cmake
Normal file
22
cmake/macros/TargetEtc2Comp.cmake
Normal file
|
@ -0,0 +1,22 @@
|
|||
#
|
||||
# Copyright 2018 High Fidelity, Inc.
|
||||
# Created by Sam Gondelman on 5/2/2018
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(TARGET_ETC2COMP)
|
||||
if (ANDROID)
|
||||
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/etc2comp)
|
||||
set(ETC2COMP_INCLUDE_DIRS "${INSTALL_DIR}/include/Etc" "${INSTALL_DIR}/include/EtcCodec")
|
||||
set(ETC2COMP_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libEtcLib.a)
|
||||
set(ETC2COMP_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libEtcLib.a)
|
||||
select_library_configurations(ETC2COMP)
|
||||
else()
|
||||
add_dependency_external_projects(etc2comp)
|
||||
find_package(Etc2Comp REQUIRED)
|
||||
endif()
|
||||
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${ETC2COMP_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${ETC2COMP_LIBRARIES})
|
||||
endmacro()
|
37
cmake/modules/FindEtc2Comp.cmake
Normal file
37
cmake/modules/FindEtc2Comp.cmake
Normal file
|
@ -0,0 +1,37 @@
|
|||
#
|
||||
# FindEtc2Comp.cmake
|
||||
#
|
||||
# Try to find the Etc2Comp compression library.
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# ETC2COMP_FOUND - system found Etc2Comp
|
||||
# ETC2COMP_INCLUDE_DIRS - the Etc2Comp include directory
|
||||
# ETC2COMP_LIBRARIES - link to this to use Etc2Comp
|
||||
#
|
||||
# Created on 5/2/2018 by Sam Gondelman
|
||||
# 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
|
||||
#
|
||||
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("etc2comp")
|
||||
|
||||
find_path(ETC_INCLUDE_DIR NAMES Etc.h HINTS ${ETC2COMP_SEARCH_DIRS})
|
||||
find_path(ETCCODEC_INCLUDE_DIR NAMES EtcBlock4x4.h HINTS ${ETC2COMP_SEARCH_DIRS})
|
||||
set(ETC2COMP_INCLUDE_DIRS "${ETC_INCLUDE_DIR}" "${ETCCODEC_INCLUDE_DIR}")
|
||||
|
||||
find_library(ETC2COMP_LIBRARY_DEBUG NAMES ETC2COMP ETC2COMP_LIB PATH_SUFFIXES EtcLib/Debug HINTS ${ETC2COMP_SEARCH_DIRS})
|
||||
find_library(ETC2COMP_LIBRARY_RELEASE NAMES ETC2COMP ETC2COMP_LIB PATH_SUFFIXES EtcLib/Release EtcLib HINTS ${ETC2COMP_SEARCH_DIRS})
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(ETC2COMP)
|
||||
|
||||
set(ETC2COMP_LIBRARIES ${ETC2COMP_LIBRARY})
|
||||
|
||||
find_package_handle_standard_args(ETC2COMP "Could NOT find ETC2COMP, try to set the path to ETC2COMP root folder in the system variable ETC2COMP_ROOT_DIR or create a directory etc2comp in HIFI_LIB_DIR and paste the necessary files there"
|
||||
ETC2COMP_INCLUDE_DIRS ETC2COMP_LIBRARIES)
|
||||
|
||||
mark_as_advanced(ETC2COMP_INCLUDE_DIRS ETC2COMP_LIBRARIES ETC2COMP_SEARCH_DIRS)
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"releaseType": "@RELEASE_TYPE@",
|
||||
"buildIdentifier": "@BUILD_VERSION@",
|
||||
"organization": "@BUILD_ORGANIZATION@",
|
||||
"appUserModelId": "@APP_USER_MODEL_ID@"
|
||||
}
|
||||
|
|
|
@ -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.";
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#include <openssl/x509.h>
|
||||
#include <random>
|
||||
|
||||
#include <QDataStream>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <Assignment.h>
|
||||
|
||||
|
@ -479,7 +481,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
limitedNodeList->killNodeWithUUID(existingNodeID);
|
||||
}
|
||||
|
||||
// add the connecting node (or re-use the matched one from eachNodeBreakable above)
|
||||
// add the connecting node
|
||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
|
||||
|
||||
// set the edit rights for this user
|
||||
|
@ -508,26 +510,22 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
return newNode;
|
||||
}
|
||||
|
||||
SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
QUuid nodeID) {
|
||||
SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection) {
|
||||
HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr;
|
||||
SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID);
|
||||
|
||||
if (connectedPeer) {
|
||||
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
|
||||
nodeID = nodeConnection.connectUUID;
|
||||
|
||||
if (connectedPeer->getActiveSocket()) {
|
||||
// set their discovered socket to whatever the activated socket on the network peer object was
|
||||
discoveredSocket = *connectedPeer->getActiveSocket();
|
||||
}
|
||||
} else {
|
||||
// we got a connectUUID we didn't recognize, either use the hinted node ID or randomly generate a new one
|
||||
if (nodeID.isNull()) {
|
||||
nodeID = QUuid::createUuid();
|
||||
}
|
||||
if (connectedPeer && connectedPeer->getActiveSocket()) {
|
||||
// set their discovered socket to whatever the activated socket on the network peer object was
|
||||
discoveredSocket = *connectedPeer->getActiveSocket();
|
||||
}
|
||||
|
||||
// create a new node ID for the verified connecting node
|
||||
auto nodeID = QUuid::createUuid();
|
||||
|
||||
// add a mapping from connection node ID to ICE peer ID
|
||||
// so that we can remove the ICE peer once we see this node connect
|
||||
_nodeToICEPeerIDs.insert(nodeID, nodeConnection.connectUUID);
|
||||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
Node::LocalID newLocalID = findOrCreateLocalID(nodeID);
|
||||
|
@ -541,6 +539,15 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
|
|||
return newNode;
|
||||
}
|
||||
|
||||
void DomainGatekeeper::cleanupICEPeerForNode(const QUuid& nodeID) {
|
||||
// remove this node ID from our node to ICE peer ID map
|
||||
// and the associated ICE peer (if it still exists)
|
||||
auto icePeerID = _nodeToICEPeerIDs.take(nodeID);
|
||||
if (!icePeerID.isNull()) {
|
||||
_icePeers.remove(icePeerID);
|
||||
}
|
||||
}
|
||||
|
||||
bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||
const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
|
|
|
@ -39,8 +39,8 @@ public:
|
|||
void addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID,
|
||||
const QUuid& walletUUID, const QString& nodeVersion);
|
||||
QUuid assignmentUUIDForPendingAssignment(const QUuid& tempUUID);
|
||||
|
||||
void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); }
|
||||
|
||||
void cleanupICEPeerForNode(const QUuid& nodeID);
|
||||
|
||||
Node::LocalID findOrCreateLocalID(const QUuid& uuid);
|
||||
|
||||
|
@ -77,8 +77,7 @@ private:
|
|||
SharedNodePointer processAgentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
const QString& username,
|
||||
const QByteArray& usernameSignature);
|
||||
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
QUuid nodeID = QUuid());
|
||||
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection);
|
||||
|
||||
bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr);
|
||||
|
@ -101,6 +100,10 @@ private:
|
|||
std::unordered_map<QUuid, PendingAssignedNodeData> _pendingAssignedNodes;
|
||||
|
||||
QHash<QUuid, SharedNetworkPeer> _icePeers;
|
||||
|
||||
using ConnectingNodeID = QUuid;
|
||||
using ICEPeerID = QUuid;
|
||||
QHash<ConnectingNodeID, ICEPeerID> _nodeToICEPeerIDs;
|
||||
|
||||
QHash<QString, QUuid> _connectionTokenHash;
|
||||
|
||||
|
|
|
@ -149,7 +149,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
QCoreApplication(argc, argv),
|
||||
_gatekeeper(this),
|
||||
_httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
|
||||
_httpsManager(NULL),
|
||||
_allAssignments(),
|
||||
_unfulfilledAssignments(),
|
||||
_isUsingDTLS(false),
|
||||
|
@ -177,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;
|
||||
|
||||
|
@ -308,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)));
|
||||
});
|
||||
|
||||
|
@ -385,6 +384,8 @@ DomainServer::~DomainServer() {
|
|||
_contentManager->terminate();
|
||||
}
|
||||
|
||||
DependencyManager::destroy<AccountManager>();
|
||||
|
||||
// cleanup the AssetClient thread
|
||||
DependencyManager::destroy<AssetClient>();
|
||||
_assetClientThread.quit();
|
||||
|
@ -439,7 +440,7 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
|||
QSslCertificate sslCertificate(&certFile);
|
||||
QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8());
|
||||
|
||||
_httpsManager = new HTTPSManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this, this);
|
||||
_httpsManager.reset(new HTTPSManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this));
|
||||
|
||||
qDebug() << "TCP server listening for HTTPS connections on" << DOMAIN_SERVER_HTTPS_PORT;
|
||||
|
||||
|
@ -990,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
|
||||
|
@ -1017,15 +1014,22 @@ void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> mess
|
|||
sendingNode->setPublicSocket(nodeRequestData.publicSockAddr);
|
||||
sendingNode->setLocalSocket(nodeRequestData.localSockAddr);
|
||||
|
||||
// update the NodeInterestSet in case there have been any changes
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
|
||||
|
||||
if (!nodeData->hasCheckedIn()) {
|
||||
nodeData->setHasCheckedIn(true);
|
||||
|
||||
// on first check in, make sure we've cleaned up any ICE peer for this node
|
||||
_gatekeeper.cleanupICEPeerForNode(sendingNode->getUUID());
|
||||
}
|
||||
|
||||
// guard against patched agents asking to hear about other agents
|
||||
auto safeInterestSet = nodeRequestData.interestList.toSet();
|
||||
if (sendingNode->getType() == NodeType::Agent) {
|
||||
safeInterestSet.remove(NodeType::Agent);
|
||||
}
|
||||
|
||||
// update the NodeInterestSet in case there have been any changes
|
||||
nodeData->setNodeInterestSet(safeInterestSet);
|
||||
|
||||
// update the connecting hostname in case it has changed
|
||||
|
@ -1114,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
|
||||
|
@ -2676,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;
|
||||
}
|
||||
|
@ -2938,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() });
|
||||
|
@ -2945,7 +2955,7 @@ void DomainServer::nodeAdded(SharedNodePointer node) {
|
|||
|
||||
void DomainServer::nodeKilled(SharedNodePointer node) {
|
||||
// if this peer connected via ICE then remove them from our ICE peers hash
|
||||
_gatekeeper.removeICEPeer(node->getUUID());
|
||||
_gatekeeper.cleanupICEPeerForNode(node->getUUID());
|
||||
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
|
@ -2978,6 +2988,8 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
broadcastNodeDisconnect(node);
|
||||
}
|
||||
|
||||
SharedAssignmentPointer DomainServer::dequeueMatchingAssignment(const QUuid& assignmentUUID, NodeType_t nodeType) {
|
||||
|
@ -3163,18 +3175,23 @@ void DomainServer::handleKillNode(SharedNodePointer nodeToKill) {
|
|||
const QUuid& nodeUUID = nodeToKill->getUUID();
|
||||
|
||||
limitedNodeList->killNodeWithUUID(nodeUUID);
|
||||
}
|
||||
|
||||
static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID);
|
||||
void DomainServer::broadcastNodeDisconnect(const SharedNodePointer& disconnectedNode) {
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID, true);
|
||||
|
||||
removedNodePacket->reset();
|
||||
removedNodePacket->write(nodeUUID.toRfc4122());
|
||||
removedNodePacket->write(disconnectedNode->getUUID().toRfc4122());
|
||||
|
||||
// broadcast out the DomainServerRemovedNode message
|
||||
limitedNodeList->eachMatchingNode([this, &nodeToKill](const SharedNodePointer& otherNode) -> bool {
|
||||
limitedNodeList->eachMatchingNode([this, &disconnectedNode](const SharedNodePointer& otherNode) -> bool {
|
||||
// only send the removed node packet to nodes that care about the type of node this was
|
||||
return isInInterestSet(otherNode, nodeToKill);
|
||||
return isInInterestSet(otherNode, disconnectedNode);
|
||||
}, [&limitedNodeList](const SharedNodePointer& otherNode){
|
||||
limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode);
|
||||
auto removedNodePacketCopy = NLPacket::createCopy(*removedNodePacket);
|
||||
limitedNodeList->sendPacket(std::move(removedNodePacketCopy), *otherNode);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
@ -165,6 +167,7 @@ private:
|
|||
unsigned int countConnectedUsers();
|
||||
|
||||
void handleKillNode(SharedNodePointer nodeToKill);
|
||||
void broadcastNodeDisconnect(const SharedNodePointer& disconnnectedNode);
|
||||
|
||||
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
|
@ -219,7 +222,7 @@ private:
|
|||
DomainGatekeeper _gatekeeper;
|
||||
|
||||
HTTPManager _httpManager;
|
||||
HTTPSManager* _httpsManager;
|
||||
std::unique_ptr<HTTPSManager> _httpsManager;
|
||||
|
||||
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
|
||||
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
|
||||
|
|
|
@ -67,8 +67,11 @@ public:
|
|||
const QString& getPlaceName() { return _placeName; }
|
||||
void setPlaceName(const QString& placeName) { _placeName = placeName; }
|
||||
|
||||
bool wasAssigned() const { return _wasAssigned; };
|
||||
bool wasAssigned() const { return _wasAssigned; }
|
||||
void setWasAssigned(bool wasAssigned) { _wasAssigned = wasAssigned; }
|
||||
|
||||
bool hasCheckedIn() const { return _hasCheckedIn; }
|
||||
void setHasCheckedIn(bool hasCheckedIn) { _hasCheckedIn = hasCheckedIn; }
|
||||
|
||||
private:
|
||||
QJsonObject overrideValuesIfNeeded(const QJsonObject& newStats);
|
||||
|
@ -94,6 +97,8 @@ private:
|
|||
QString _placeName;
|
||||
|
||||
bool _wasAssigned { false };
|
||||
|
||||
bool _hasCheckedIn { false };
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServerNodeData_h
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
|
|
@ -133,13 +133,7 @@ if (APPLE)
|
|||
# set where in the bundle to put the resources file
|
||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
|
||||
set(DISCOVERED_RESOURCES "")
|
||||
|
||||
# use the add_resources_to_os_x_bundle macro to recurse into resources
|
||||
add_resources_to_os_x_bundle("${CMAKE_CURRENT_SOURCE_DIR}/resources")
|
||||
|
||||
# append the discovered resources to our list of interface sources
|
||||
list(APPEND INTERFACE_SRCS ${DISCOVERED_RESOURCES})
|
||||
list(APPEND INTERFACE_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME})
|
||||
endif()
|
||||
|
||||
|
@ -316,18 +310,27 @@ if (APPLE)
|
|||
set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources")
|
||||
set(RESOURCES_DEV_DIR "$<TARGET_FILE_DIR:${TARGET_NAME}>/../Resources")
|
||||
|
||||
# copy script files beside the executable
|
||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
|
||||
# copy script files beside the executable
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
"${CMAKE_SOURCE_DIR}/scripts"
|
||||
"${RESOURCES_DEV_DIR}/scripts"
|
||||
)
|
||||
|
||||
# copy JSDoc files beside the executable
|
||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
|
||||
"${CMAKE_SOURCE_DIR}/scripts"
|
||||
"${RESOURCES_DEV_DIR}/scripts"
|
||||
# copy JSDoc files beside the executable
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
"${CMAKE_SOURCE_DIR}/tools/jsdoc/out"
|
||||
"${RESOURCES_DEV_DIR}/jsdoc"
|
||||
"${CMAKE_SOURCE_DIR}/tools/jsdoc/out"
|
||||
"${RESOURCES_DEV_DIR}/jsdoc"
|
||||
# copy the resources files beside the executable
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||
"${RESOURCES_RCC}"
|
||||
"${RESOURCES_DEV_DIR}"
|
||||
# FIXME, the edit script code loads HTML from the scripts folder
|
||||
# which in turn relies on CSS that refers to the fonts. In theory
|
||||
# we should be able to modify the CSS to reference the QRC path to
|
||||
# the ttf files, but doing so generates a CORS policy violation,
|
||||
# so we have to retain a copy of the fonts outside of the resources binary
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
"${PROJECT_SOURCE_DIR}/resources/fonts"
|
||||
"${RESOURCES_DEV_DIR}/fonts"
|
||||
)
|
||||
|
||||
# call the fixup_interface macro to add required bundling commands for installation
|
||||
|
@ -356,13 +359,10 @@ else()
|
|||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||
"${PROJECT_SOURCE_DIR}/resources/serverless/tutorial.json"
|
||||
"${RESOURCES_DEV_DIR}/serverless/tutorial.json"
|
||||
)
|
||||
|
||||
# copy JSDoc files beside the executable
|
||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
|
||||
# copy JSDoc files beside the executable
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
"${CMAKE_SOURCE_DIR}/tools/jsdoc/out"
|
||||
"${INTERFACE_EXEC_DIR}/jsdoc"
|
||||
"${CMAKE_SOURCE_DIR}/tools/jsdoc/out"
|
||||
"${INTERFACE_EXEC_DIR}/jsdoc"
|
||||
)
|
||||
|
||||
# link target to external libraries
|
||||
|
|
|
@ -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
|
||||
|
|
BIN
interface/resources/meshes/mannequin/Eyes.png
Normal file
BIN
interface/resources/meshes/mannequin/Eyes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 1,002 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
interface/resources/meshes/mannequin/lambert1_Roughness.png
Normal file
BIN
interface/resources/meshes/mannequin/lambert1_Roughness.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 525 KiB |
Binary file not shown.
Binary file not shown.
|
@ -163,10 +163,18 @@ TextField {
|
|||
text: textField.label
|
||||
colorScheme: textField.colorScheme
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
Binding on anchors.right {
|
||||
when: textField.right
|
||||
value: textField.right
|
||||
}
|
||||
Binding on wrapMode {
|
||||
when: textField.right
|
||||
value: Text.WordWrap
|
||||
}
|
||||
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: 3
|
||||
wrapMode: Text.WordWrap
|
||||
visible: label != ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ ModalWindow {
|
|||
property int iconSize: 40
|
||||
|
||||
property bool selectDirectory: false;
|
||||
property bool showHidden: false;
|
||||
property bool showHidden: true;
|
||||
// FIXME implement
|
||||
property bool multiSelect: false;
|
||||
property bool saveDialog: false;
|
||||
|
@ -324,11 +324,14 @@ ModalWindow {
|
|||
showDirsFirst: true
|
||||
showDotAndDotDot: false
|
||||
showFiles: !root.selectDirectory
|
||||
showHidden: root.showHidden
|
||||
Component.onCompleted: {
|
||||
showFiles = !root.selectDirectory
|
||||
showHidden = root.showHidden
|
||||
}
|
||||
|
||||
onFolderChanged: {
|
||||
d.clearSelection();
|
||||
fileTableModel.update(); // Update once the data from the folder change is available.
|
||||
}
|
||||
|
||||
|
@ -448,7 +451,7 @@ ModalWindow {
|
|||
rows = 0,
|
||||
i;
|
||||
|
||||
var newFilesModel = filesModelBuilder.createObject(root);
|
||||
filesModel = filesModelBuilder.createObject(root);
|
||||
|
||||
comparisonFunction = sortOrder === Qt.AscendingOrder
|
||||
? function(a, b) { return a < b; }
|
||||
|
@ -470,7 +473,7 @@ ModalWindow {
|
|||
while (lower < upper) {
|
||||
middle = Math.floor((lower + upper) / 2);
|
||||
var lessThan;
|
||||
if (comparisonFunction(sortValue, newFilesModel.get(middle)[sortField])) {
|
||||
if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) {
|
||||
lessThan = true;
|
||||
upper = middle;
|
||||
} else {
|
||||
|
@ -479,7 +482,7 @@ ModalWindow {
|
|||
}
|
||||
}
|
||||
|
||||
newFilesModel.insert(lower, {
|
||||
filesModel.insert(lower, {
|
||||
fileName: fileName,
|
||||
fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
|
||||
fileSize: model.getItem(i, "fileSize"),
|
||||
|
@ -490,9 +493,6 @@ ModalWindow {
|
|||
|
||||
rows++;
|
||||
}
|
||||
filesModel = newFilesModel;
|
||||
|
||||
d.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ ModalWindow {
|
|||
property int iconSize: 40
|
||||
|
||||
property bool selectDirectory: false;
|
||||
property bool showHidden: false;
|
||||
property bool showHidden: true;
|
||||
// FIXME implement
|
||||
property bool multiSelect: false;
|
||||
property bool saveDialog: false;
|
||||
|
@ -325,11 +325,14 @@ ModalWindow {
|
|||
showDirsFirst: true
|
||||
showDotAndDotDot: false
|
||||
showFiles: !root.selectDirectory
|
||||
showHidden: root.showHidden
|
||||
Component.onCompleted: {
|
||||
showFiles = !root.selectDirectory
|
||||
showHidden = root.showHidden
|
||||
}
|
||||
|
||||
onFolderChanged: {
|
||||
d.clearSelection();
|
||||
fileTableModel.update(); // Update once the data from the folder change is available.
|
||||
}
|
||||
|
||||
|
@ -449,7 +452,7 @@ ModalWindow {
|
|||
rows = 0,
|
||||
i;
|
||||
|
||||
var newFilesModel = filesModelBuilder.createObject(root);
|
||||
filesModel = filesModelBuilder.createObject(root);
|
||||
|
||||
comparisonFunction = sortOrder === Qt.AscendingOrder
|
||||
? function(a, b) { return a < b; }
|
||||
|
@ -471,7 +474,7 @@ ModalWindow {
|
|||
while (lower < upper) {
|
||||
middle = Math.floor((lower + upper) / 2);
|
||||
var lessThan;
|
||||
if (comparisonFunction(sortValue, newFilesModel.get(middle)[sortField])) {
|
||||
if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) {
|
||||
lessThan = true;
|
||||
upper = middle;
|
||||
} else {
|
||||
|
@ -480,7 +483,7 @@ ModalWindow {
|
|||
}
|
||||
}
|
||||
|
||||
newFilesModel.insert(lower, {
|
||||
filesModel.insert(lower, {
|
||||
fileName: fileName,
|
||||
fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
|
||||
fileSize: model.getItem(i, "fileSize"),
|
||||
|
@ -491,9 +494,6 @@ ModalWindow {
|
|||
|
||||
rows++;
|
||||
}
|
||||
filesModel = newFilesModel;
|
||||
|
||||
d.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ TabletModalWindow {
|
|||
property int iconSize: 40
|
||||
|
||||
property bool selectDirectory: false;
|
||||
property bool showHidden: false;
|
||||
property bool showHidden: true;
|
||||
// FIXME implement
|
||||
property bool multiSelect: false;
|
||||
property bool saveDialog: false;
|
||||
|
@ -288,12 +288,15 @@ TabletModalWindow {
|
|||
showDirsFirst: true
|
||||
showDotAndDotDot: false
|
||||
showFiles: !root.selectDirectory
|
||||
showHidden: root.showHidden
|
||||
Component.onCompleted: {
|
||||
showFiles = !root.selectDirectory
|
||||
showHidden = root.showHidden
|
||||
}
|
||||
|
||||
onFolderChanged: {
|
||||
fileTableModel.update()
|
||||
d.clearSelection();
|
||||
fileTableModel.update();
|
||||
}
|
||||
|
||||
function getItem(index, field) {
|
||||
|
@ -411,7 +414,7 @@ TabletModalWindow {
|
|||
rows = 0,
|
||||
i;
|
||||
|
||||
var newFilesModel = filesModelBuilder.createObject(root);
|
||||
filesModel = filesModelBuilder.createObject(root);
|
||||
|
||||
comparisonFunction = sortOrder === Qt.AscendingOrder
|
||||
? function(a, b) { return a < b; }
|
||||
|
@ -433,7 +436,7 @@ TabletModalWindow {
|
|||
while (lower < upper) {
|
||||
middle = Math.floor((lower + upper) / 2);
|
||||
var lessThan;
|
||||
if (comparisonFunction(sortValue, newFilesModel.get(middle)[sortField])) {
|
||||
if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) {
|
||||
lessThan = true;
|
||||
upper = middle;
|
||||
} else {
|
||||
|
@ -442,7 +445,7 @@ TabletModalWindow {
|
|||
}
|
||||
}
|
||||
|
||||
newFilesModel.insert(lower, {
|
||||
filesModel.insert(lower, {
|
||||
fileName: fileName,
|
||||
fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
|
||||
fileSize: model.getItem(i, "fileSize"),
|
||||
|
@ -453,9 +456,6 @@ TabletModalWindow {
|
|||
|
||||
rows++;
|
||||
}
|
||||
filesModel = newFilesModel;
|
||||
|
||||
d.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,11 @@ 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);
|
||||
}
|
||||
// Server currently doesn't give isStacked (undefined). Could give bool.
|
||||
drillDownToPlace: (data.isStacked === undefined) ? (data.action !== 'concurrency') : data.isStacked,
|
||||
isStacked: !!data.isStacked
|
||||
};
|
||||
}
|
||||
function filterChoicesByText() {
|
||||
suggestions.clear();
|
||||
placeMap = {};
|
||||
allStories.forEach(makeFilteredStoryProcessor());
|
||||
root.visible = !!suggestions.count;
|
||||
}
|
||||
|
||||
RalewayBold {
|
||||
id: label;
|
||||
|
@ -208,6 +106,7 @@ Column {
|
|||
highlightMoveDuration: -1;
|
||||
highlightMoveVelocity: -1;
|
||||
currentIndex: -1;
|
||||
onAtXEndChanged: { if (scroll.atXEnd && !scroll.atXBeginning) { suggestions.getNextPage(); } }
|
||||
|
||||
spacing: 12;
|
||||
width: parent.width;
|
||||
|
@ -227,6 +126,7 @@ Column {
|
|||
onlineUsers: model.online_users;
|
||||
storyId: model.metaverseId;
|
||||
drillDownToPlace: model.drillDownToPlace;
|
||||
isStacked: model.isStacked;
|
||||
|
||||
textPadding: root.textPadding;
|
||||
smallMargin: root.smallMargin;
|
||||
|
@ -239,21 +139,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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -319,10 +319,10 @@ Item {
|
|||
visible: thisNameCard.userName !== "";
|
||||
// Size
|
||||
width: parent.width
|
||||
height: usernameTextPixelSize + 4
|
||||
height: paintedHeight
|
||||
// Anchors
|
||||
anchors.top: isMyCard ? myDisplayName.bottom : pal.activeTab == "nearbyTab" ? displayNameContainer.bottom : undefined //(parent.height - displayNameTextPixelSize/2));
|
||||
anchors.verticalCenter: pal.activeTab == "connectionsTab" && !isMyCard ? avatarImage.verticalCenter : undefined
|
||||
anchors.top: isMyCard ? myDisplayName.bottom : pal.activeTab == "nearbyTab" ? displayNameContainer.bottom : avatarImage.top //(parent.height - displayNameTextPixelSize/2));
|
||||
anchors.bottom: pal.activeTab === "connectionsTab" && !isMyCard ? avatarImage.bottom : undefined
|
||||
anchors.left: avatarImage.right;
|
||||
anchors.leftMargin: avatarImage.visible ? 5 : 0;
|
||||
anchors.rightMargin: 5;
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
39
interface/resources/qml/hifi/RootHttpRequest.qml
Normal file
39
interface/resources/qml/hifi/RootHttpRequest.qml
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -44,7 +44,7 @@ Item {
|
|||
|
||||
Item {
|
||||
id: avatarImage;
|
||||
visible: profileUrl !== "" && userName !== "";
|
||||
visible: profilePicUrl !== "" && userName !== "";
|
||||
// Size
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.left: parent.left;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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' });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
//
|
||||
|
|
168
interface/resources/qml/hifi/models/PSFListModel.qml
Normal file
168
interface/resources/qml/hifi/models/PSFListModel.qml
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick 2.7
|
||||
import Qt.labs.folderlistmodel 2.1
|
||||
import Qt.labs.settings 1.0
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
||||
import QtQuick.Controls 1.4 as QQC1
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import ".."
|
||||
import "../../../controls-uit"
|
||||
|
@ -30,6 +30,8 @@ Rectangle {
|
|||
|
||||
color: hifi.colors.baseGray;
|
||||
|
||||
property var filesModel: ListModel { }
|
||||
|
||||
Settings {
|
||||
category: "FileDialog"
|
||||
property alias width: root.width
|
||||
|
@ -52,7 +54,7 @@ Rectangle {
|
|||
property int iconSize: 40
|
||||
|
||||
property bool selectDirectory: false;
|
||||
property bool showHidden: false;
|
||||
property bool showHidden: true;
|
||||
// FIXME implement
|
||||
property bool multiSelect: false;
|
||||
property bool saveDialog: false;
|
||||
|
@ -149,7 +151,7 @@ Rectangle {
|
|||
|
||||
ComboBox {
|
||||
id: pathSelector
|
||||
anchors {
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: hifi.dimensions.contentMargin.y
|
||||
left: navControls.right
|
||||
|
@ -247,7 +249,9 @@ Rectangle {
|
|||
}
|
||||
|
||||
currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath);
|
||||
currentSelectionIsFolder = fileTableView.model.isFolder(row);
|
||||
currentSelectionIsFolder = fileTableView.model !== filesModel ?
|
||||
fileTableView.model.isFolder(row) :
|
||||
fileTableModel.isFolder(row);
|
||||
if (root.selectDirectory || !currentSelectionIsFolder) {
|
||||
currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl));
|
||||
} else {
|
||||
|
@ -280,11 +284,14 @@ Rectangle {
|
|||
showDirsFirst: true
|
||||
showDotAndDotDot: false
|
||||
showFiles: !root.selectDirectory
|
||||
showHidden: root.showHidden
|
||||
Component.onCompleted: {
|
||||
showFiles = !root.selectDirectory
|
||||
showHidden = root.showHidden
|
||||
}
|
||||
|
||||
onFolderChanged: {
|
||||
d.clearSelection();
|
||||
fileTableModel.update(); // Update once the data from the folder change is available.
|
||||
}
|
||||
|
||||
|
@ -325,7 +332,12 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
Component {
|
||||
id: filesModelBuilder
|
||||
ListModel { }
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: fileTableModel
|
||||
|
||||
// FolderListModel has a couple of problems:
|
||||
|
@ -377,7 +389,11 @@ Rectangle {
|
|||
if (row === -1) {
|
||||
return false;
|
||||
}
|
||||
return get(row).fileIsDir;
|
||||
return filesModel.get(row).fileIsDir;
|
||||
}
|
||||
|
||||
function get(row) {
|
||||
return filesModel.get(row)
|
||||
}
|
||||
|
||||
function update() {
|
||||
|
@ -395,7 +411,7 @@ Rectangle {
|
|||
rows = 0,
|
||||
i;
|
||||
|
||||
clear();
|
||||
filesModel = filesModelBuilder.createObject(root);
|
||||
|
||||
comparisonFunction = sortOrder === Qt.AscendingOrder
|
||||
? function(a, b) { return a < b; }
|
||||
|
@ -417,7 +433,7 @@ Rectangle {
|
|||
while (lower < upper) {
|
||||
middle = Math.floor((lower + upper) / 2);
|
||||
var lessThan;
|
||||
if (comparisonFunction(sortValue, get(middle)[sortField])) {
|
||||
if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) {
|
||||
lessThan = true;
|
||||
upper = middle;
|
||||
} else {
|
||||
|
@ -426,7 +442,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
insert(lower, {
|
||||
filesModel.insert(lower, {
|
||||
fileName: fileName,
|
||||
fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
|
||||
fileSize: model.getItem(i, "fileSize"),
|
||||
|
@ -437,8 +453,6 @@ Rectangle {
|
|||
|
||||
rows++;
|
||||
}
|
||||
|
||||
d.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -463,12 +477,12 @@ Rectangle {
|
|||
sortIndicatorOrder: Qt.AscendingOrder
|
||||
sortIndicatorVisible: true
|
||||
|
||||
model: fileTableModel
|
||||
model: filesModel
|
||||
|
||||
function updateSort() {
|
||||
model.sortOrder = sortIndicatorOrder;
|
||||
model.sortColumn = sortIndicatorColumn;
|
||||
model.update();
|
||||
fileTableModel.sortOrder = sortIndicatorOrder;
|
||||
fileTableModel.sortColumn = sortIndicatorColumn;
|
||||
fileTableModel.update();
|
||||
}
|
||||
|
||||
onSortIndicatorColumnChanged: { updateSort(); }
|
||||
|
@ -520,7 +534,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
TableViewColumn {
|
||||
QQC1.TableViewColumn {
|
||||
id: fileNameColumn
|
||||
role: "fileName"
|
||||
title: "Name"
|
||||
|
@ -528,7 +542,7 @@ Rectangle {
|
|||
movable: false
|
||||
resizable: true
|
||||
}
|
||||
TableViewColumn {
|
||||
QQC1.TableViewColumn {
|
||||
id: fileMofifiedColumn
|
||||
role: "fileModified"
|
||||
title: "Date"
|
||||
|
@ -537,7 +551,7 @@ Rectangle {
|
|||
resizable: true
|
||||
visible: !selectDirectory
|
||||
}
|
||||
TableViewColumn {
|
||||
QQC1.TableViewColumn {
|
||||
role: "fileSize"
|
||||
title: "Size"
|
||||
width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width
|
||||
|
@ -552,11 +566,12 @@ Rectangle {
|
|||
}
|
||||
|
||||
function navigateToCurrentRow() {
|
||||
var currentModel = fileTableView.model !== filesModel ? fileTableView.model : fileTableModel
|
||||
var row = fileTableView.currentRow
|
||||
var isFolder = model.isFolder(row);
|
||||
var file = model.get(row).filePath;
|
||||
var isFolder = currentModel.isFolder(row);
|
||||
var file = currentModel.get(row).filePath;
|
||||
if (isFolder) {
|
||||
fileTableView.model.folder = helper.pathToUrl(file);
|
||||
currentModel.folder = helper.pathToUrl(file);
|
||||
} else {
|
||||
okAction.trigger();
|
||||
}
|
||||
|
@ -571,7 +586,8 @@ Rectangle {
|
|||
var newPrefix = prefix + event.text.toLowerCase();
|
||||
var matchedIndex = -1;
|
||||
for (var i = 0; i < model.count; ++i) {
|
||||
var name = model.get(i).fileName.toLowerCase();
|
||||
var name = model !== filesModel ? model.get(i).fileName.toLowerCase() :
|
||||
filesModel.get(i).fileName.toLowerCase();
|
||||
if (0 === name.indexOf(newPrefix)) {
|
||||
matchedIndex = i;
|
||||
break;
|
||||
|
|
|
@ -8,48 +8,46 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AboutUtil.h"
|
||||
#include <QDate>
|
||||
#include <QLocale>
|
||||
|
||||
#include "AboutUtil.h"
|
||||
#include "BuildInfo.h"
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
#include <OffscreenQmlDialog.h>
|
||||
|
||||
#include "BuildInfo.h"
|
||||
#include "DependencyManager.h"
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
#include "Application.h"
|
||||
#include <OffscreenQmlDialog.h>
|
||||
|
||||
AboutUtil::AboutUtil(QObject *parent) : QObject(parent) {
|
||||
QLocale locale_;
|
||||
m_DateConverted = QDate::fromString(BuildInfo::BUILD_TIME, "dd/MM/yyyy").
|
||||
toString(locale_.dateFormat(QLocale::ShortFormat));
|
||||
QLocale locale;
|
||||
_dateConverted = QDate::fromString(BuildInfo::BUILD_TIME, "dd/MM/yyyy").
|
||||
toString(locale.dateFormat(QLocale::ShortFormat));
|
||||
}
|
||||
|
||||
AboutUtil *AboutUtil::getInstance()
|
||||
{
|
||||
AboutUtil *AboutUtil::getInstance() {
|
||||
static AboutUtil instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
QString AboutUtil::buildDate() const
|
||||
{
|
||||
return m_DateConverted;
|
||||
QString AboutUtil::getBuildDate() const {
|
||||
return _dateConverted;
|
||||
}
|
||||
|
||||
QString AboutUtil::buildVersion() const
|
||||
{
|
||||
QString AboutUtil::getBuildVersion() const {
|
||||
return BuildInfo::VERSION;
|
||||
}
|
||||
|
||||
QString AboutUtil::qtVersion() const
|
||||
{
|
||||
QString AboutUtil::getQtVersion() const {
|
||||
return qVersion();
|
||||
}
|
||||
|
||||
void AboutUtil::openUrl(const QString& url) const {
|
||||
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||
auto tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system");
|
||||
auto hmd = DependencyManager::get<HMDScriptingInterface>();
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
|
||||
|
|
|
@ -15,28 +15,41 @@
|
|||
|
||||
#include <QObject>
|
||||
|
||||
class AboutUtil : public QObject {
|
||||
/**jsdoc
|
||||
* @namespace HifiAbout
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
*
|
||||
* @property {string} buildDate
|
||||
* @property {string} buildVersion
|
||||
* @property {string} qtVersion
|
||||
*/
|
||||
|
||||
class AboutUtil : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString buildDate READ buildDate CONSTANT)
|
||||
Q_PROPERTY(QString buildVersion READ buildVersion CONSTANT)
|
||||
Q_PROPERTY(QString qtVersion READ qtVersion CONSTANT)
|
||||
|
||||
AboutUtil(QObject* parent = nullptr);
|
||||
Q_PROPERTY(QString buildDate READ getBuildDate CONSTANT)
|
||||
Q_PROPERTY(QString buildVersion READ getBuildVersion CONSTANT)
|
||||
Q_PROPERTY(QString qtVersion READ getQtVersion CONSTANT)
|
||||
public:
|
||||
static AboutUtil* getInstance();
|
||||
~AboutUtil() {}
|
||||
|
||||
QString buildDate() const;
|
||||
QString buildVersion() const;
|
||||
QString qtVersion() const;
|
||||
QString getBuildDate() const;
|
||||
QString getBuildVersion() const;
|
||||
QString getQtVersion() const;
|
||||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* @function HifiAbout.openUrl
|
||||
* @param {string} url
|
||||
*/
|
||||
void openUrl(const QString &url) const;
|
||||
private:
|
||||
|
||||
QString m_DateConverted;
|
||||
AboutUtil(QObject* parent = nullptr);
|
||||
QString _dateConverted;
|
||||
};
|
||||
|
||||
#endif // hifi_AboutUtil_h
|
||||
|
|
|
@ -334,7 +334,11 @@ static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" };
|
|||
static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
|
||||
#endif
|
||||
|
||||
#if !defined(Q_OS_ANDROID)
|
||||
static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16;
|
||||
#else
|
||||
static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 4;
|
||||
#endif
|
||||
|
||||
// For processing on QThreadPool, we target a number of threads after reserving some
|
||||
// based on how many are being consumed by the application and the display plugin. However,
|
||||
|
@ -693,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>
|
||||
|
@ -717,7 +721,7 @@ private:
|
|||
* <tr><td><code>NavigationFocused</code></td><td>number</td><td>number</td><td><em>Not used.</em></td></tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @typedef Controller.Hardware-Application
|
||||
* @typedef {object} Controller.Hardware-Application
|
||||
*/
|
||||
|
||||
static const QString STATE_IN_HMD = "InHMD";
|
||||
|
@ -776,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]);
|
||||
|
@ -798,15 +802,8 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
qApp->setProperty(hifi::properties::APP_LOCAL_DATA_PATH, cacheDir);
|
||||
}
|
||||
|
||||
// FIXME fix the OSX installer to install the resources.rcc binary instead of resource files and remove
|
||||
// this conditional exclusion
|
||||
#if !defined(Q_OS_OSX)
|
||||
{
|
||||
#if defined(Q_OS_ANDROID)
|
||||
const QString resourcesBinaryFile = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/resources.rcc";
|
||||
#else
|
||||
const QString resourcesBinaryFile = QCoreApplication::applicationDirPath() + "/resources.rcc";
|
||||
#endif
|
||||
const QString resourcesBinaryFile = PathUtils::getRccPath();
|
||||
if (!QFile::exists(resourcesBinaryFile)) {
|
||||
throw std::runtime_error("Unable to find primary resources");
|
||||
}
|
||||
|
@ -814,7 +811,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
throw std::runtime_error("Unable to load primary resources");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Tell the plugin manager about our statically linked plugins
|
||||
auto pluginManager = PluginManager::getInstance();
|
||||
|
@ -1112,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.";
|
||||
|
@ -1369,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.");
|
||||
|
@ -1417,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";
|
||||
|
@ -1443,17 +1439,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
// add firstRun flag from settings to launch event
|
||||
Setting::Handle<bool> firstRun { Settings::firstRun, true };
|
||||
|
||||
// once the settings have been loaded, check if we need to flip the default for UserActivityLogger
|
||||
auto& userActivityLogger = UserActivityLogger::getInstance();
|
||||
if (!userActivityLogger.isDisabledSettingSet()) {
|
||||
// the user activity logger is opt-out for Interface
|
||||
// but it's defaulted to disabled for other targets
|
||||
// so we need to enable it here if it has never been disabled by the user
|
||||
userActivityLogger.disable(false);
|
||||
}
|
||||
|
||||
QString machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint());
|
||||
|
||||
auto& userActivityLogger = UserActivityLogger::getInstance();
|
||||
if (userActivityLogger.isEnabled()) {
|
||||
// sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
|
||||
// The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
|
||||
|
@ -1465,6 +1453,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() },
|
||||
|
@ -2182,7 +2171,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);
|
||||
|
||||
|
@ -2399,7 +2388,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());
|
||||
|
||||
|
@ -2492,6 +2481,7 @@ void Application::cleanupBeforeQuit() {
|
|||
}
|
||||
|
||||
_window->saveGeometry();
|
||||
_gpuContext->shutdown();
|
||||
|
||||
// Destroy third party processes after scripts have finished using them.
|
||||
#ifdef HAVE_DDE
|
||||
|
@ -2609,10 +2599,55 @@ void Application::initializeGL() {
|
|||
_isGLInitialized = true;
|
||||
}
|
||||
|
||||
_glWidget->makeCurrent();
|
||||
glClearColor(0.2f, 0.2f, 0.2f, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
_glWidget->swapBuffers();
|
||||
if (!_glWidget->makeCurrent()) {
|
||||
qCWarning(interfaceapp, "Unable to make window context current");
|
||||
}
|
||||
|
||||
#if !defined(DISABLE_QML)
|
||||
// Build a shared canvas / context for the Chromium processes
|
||||
{
|
||||
// Disable signed distance field font rendering on ATI/AMD GPUs, due to
|
||||
// https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app
|
||||
std::string vendor{ (const char*)glGetString(GL_VENDOR) };
|
||||
if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) {
|
||||
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text"));
|
||||
}
|
||||
|
||||
// Chromium rendering uses some GL functions that prevent nSight from capturing
|
||||
// frames, so we only create the shared context if nsight is NOT active.
|
||||
if (!nsightActive()) {
|
||||
_chromiumShareContext = new OffscreenGLCanvas();
|
||||
_chromiumShareContext->setObjectName("ChromiumShareContext");
|
||||
_chromiumShareContext->create(_glWidget->qglContext());
|
||||
if (!_chromiumShareContext->makeCurrent()) {
|
||||
qCWarning(interfaceapp, "Unable to make chromium shared context current");
|
||||
}
|
||||
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
|
||||
_chromiumShareContext->doneCurrent();
|
||||
// Restore the GL widget context
|
||||
if (!_glWidget->makeCurrent()) {
|
||||
qCWarning(interfaceapp, "Unable to make window context current");
|
||||
}
|
||||
} else {
|
||||
qCWarning(interfaceapp) << "nSight detected, disabling chrome rendering";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Build a shared canvas / context for the QML rendering
|
||||
{
|
||||
_qmlShareContext = new OffscreenGLCanvas();
|
||||
_qmlShareContext->setObjectName("QmlShareContext");
|
||||
_qmlShareContext->create(_glWidget->qglContext());
|
||||
if (!_qmlShareContext->makeCurrent()) {
|
||||
qCWarning(interfaceapp, "Unable to make QML shared context current");
|
||||
}
|
||||
OffscreenQmlSurface::setSharedContext(_qmlShareContext->getContext());
|
||||
_qmlShareContext->doneCurrent();
|
||||
if (!_glWidget->makeCurrent()) {
|
||||
qCWarning(interfaceapp, "Unable to make window context current");
|
||||
}
|
||||
}
|
||||
|
||||
// Build an offscreen GL context for the main thread.
|
||||
_offscreenContext = new OffscreenGLCanvas();
|
||||
|
@ -2624,6 +2659,11 @@ void Application::initializeGL() {
|
|||
_offscreenContext->doneCurrent();
|
||||
_offscreenContext->setThreadContext();
|
||||
|
||||
_glWidget->makeCurrent();
|
||||
glClearColor(0.2f, 0.2f, 0.2f, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
_glWidget->swapBuffers();
|
||||
|
||||
// Move the GL widget context to the render event handler thread
|
||||
_renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
|
||||
if (!_offscreenContext->makeCurrent()) {
|
||||
|
@ -2633,7 +2673,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>();
|
||||
|
@ -2656,7 +2696,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);
|
||||
|
@ -2669,7 +2709,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
|
||||
|
@ -2746,40 +2786,6 @@ extern void setupPreferences();
|
|||
static void addDisplayPluginToMenu(const DisplayPluginPointer& displayPlugin, int index, bool active = false);
|
||||
|
||||
void Application::initializeUi() {
|
||||
// Build a shared canvas / context for the Chromium processes
|
||||
#if !defined(DISABLE_QML)
|
||||
// Chromium rendering uses some GL functions that prevent nSight from capturing
|
||||
// frames, so we only create the shared context if nsight is NOT active.
|
||||
if (!nsightActive()) {
|
||||
_chromiumShareContext = new OffscreenGLCanvas();
|
||||
_chromiumShareContext->setObjectName("ChromiumShareContext");
|
||||
_chromiumShareContext->create(_offscreenContext->getContext());
|
||||
if (!_chromiumShareContext->makeCurrent()) {
|
||||
qCWarning(interfaceapp, "Unable to make chromium shared context current");
|
||||
}
|
||||
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
|
||||
_chromiumShareContext->doneCurrent();
|
||||
// Restore the GL widget context
|
||||
_offscreenContext->makeCurrent();
|
||||
} else {
|
||||
qCWarning(interfaceapp) << "nSIGHT detected, disabling chrome rendering";
|
||||
}
|
||||
#endif
|
||||
|
||||
// Build a shared canvas / context for the QML rendering
|
||||
_qmlShareContext = new OffscreenGLCanvas();
|
||||
_qmlShareContext->setObjectName("QmlShareContext");
|
||||
_qmlShareContext->create(_offscreenContext->getContext());
|
||||
if (!_qmlShareContext->makeCurrent()) {
|
||||
qCWarning(interfaceapp, "Unable to make QML shared context current");
|
||||
}
|
||||
OffscreenQmlSurface::setSharedContext(_qmlShareContext->getContext());
|
||||
_qmlShareContext->doneCurrent();
|
||||
// Restore the GL widget context
|
||||
_offscreenContext->makeCurrent();
|
||||
// Make sure all QML surfaces share the main thread GL context
|
||||
OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext());
|
||||
|
||||
AddressBarDialog::registerType();
|
||||
ErrorDialog::registerType();
|
||||
LoginDialog::registerType();
|
||||
|
@ -3011,9 +3017,11 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) {
|
|||
auto surfaceContext = DependencyManager::get<OffscreenUi>()->getSurfaceContext();
|
||||
surfaceContext->setContextProperty("Stats", Stats::getInstance());
|
||||
|
||||
#if !defined(Q_OS_ANDROID)
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto qml = PathUtils::qmlUrl("AvatarInputsBar.qml");
|
||||
offscreenUi->show(qml, "AvatarInputsBar");
|
||||
#endif
|
||||
}
|
||||
|
||||
void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
|
||||
|
@ -3642,7 +3650,6 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
_keysPressed.insert(event->key());
|
||||
|
||||
_controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts
|
||||
|
||||
// if one of our scripts have asked to capture this event, then stop processing it
|
||||
if (_controllerScriptingInterface->isKeyCaptured(event)) {
|
||||
return;
|
||||
|
@ -3668,9 +3675,21 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_1:
|
||||
case Qt::Key_2:
|
||||
case Qt::Key_3:
|
||||
case Qt::Key_1: {
|
||||
Menu* menu = Menu::getInstance();
|
||||
menu->triggerOption(MenuOption::FirstPerson);
|
||||
break;
|
||||
}
|
||||
case Qt::Key_2: {
|
||||
Menu* menu = Menu::getInstance();
|
||||
menu->triggerOption(MenuOption::FullscreenMirror);
|
||||
break;
|
||||
}
|
||||
case Qt::Key_3: {
|
||||
Menu* menu = Menu::getInstance();
|
||||
menu->triggerOption(MenuOption::ThirdPerson);
|
||||
break;
|
||||
}
|
||||
case Qt::Key_4:
|
||||
case Qt::Key_5:
|
||||
case Qt::Key_6:
|
||||
|
@ -3727,6 +3746,13 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_R:
|
||||
if (isMeta && !event->isAutoRepeat()) {
|
||||
DependencyManager::get<ScriptEngines>()->reloadAllScripts();
|
||||
DependencyManager::get<OffscreenUi>()->clearCache();
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_Asterisk:
|
||||
Menu::getInstance()->triggerOption(MenuOption::DefaultSkybox);
|
||||
break;
|
||||
|
@ -3792,68 +3818,6 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
Menu::getInstance()->triggerOption(MenuOption::Chat);
|
||||
break;
|
||||
|
||||
#if 0
|
||||
case Qt::Key_I:
|
||||
if (isShifted) {
|
||||
_myCamera.setEyeOffsetOrientation(glm::normalize(
|
||||
glm::quat(glm::vec3(0.002f, 0, 0)) * _myCamera.getEyeOffsetOrientation()));
|
||||
} else {
|
||||
_myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, 0.001, 0));
|
||||
}
|
||||
updateProjectionMatrix();
|
||||
break;
|
||||
|
||||
case Qt::Key_K:
|
||||
if (isShifted) {
|
||||
_myCamera.setEyeOffsetOrientation(glm::normalize(
|
||||
glm::quat(glm::vec3(-0.002f, 0, 0)) * _myCamera.getEyeOffsetOrientation()));
|
||||
} else {
|
||||
_myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, -0.001, 0));
|
||||
}
|
||||
updateProjectionMatrix();
|
||||
break;
|
||||
|
||||
case Qt::Key_J:
|
||||
if (isShifted) {
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
_viewFrustum.setFocalLength(_viewFrustum.getFocalLength() - 0.1f);
|
||||
} else {
|
||||
_myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(-0.001, 0, 0));
|
||||
}
|
||||
updateProjectionMatrix();
|
||||
break;
|
||||
|
||||
case Qt::Key_M:
|
||||
if (isShifted) {
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
_viewFrustum.setFocalLength(_viewFrustum.getFocalLength() + 0.1f);
|
||||
} else {
|
||||
_myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0.001, 0, 0));
|
||||
}
|
||||
updateProjectionMatrix();
|
||||
break;
|
||||
|
||||
case Qt::Key_U:
|
||||
if (isShifted) {
|
||||
_myCamera.setEyeOffsetOrientation(glm::normalize(
|
||||
glm::quat(glm::vec3(0, 0, -0.002f)) * _myCamera.getEyeOffsetOrientation()));
|
||||
} else {
|
||||
_myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, 0, -0.001));
|
||||
}
|
||||
updateProjectionMatrix();
|
||||
break;
|
||||
|
||||
case Qt::Key_Y:
|
||||
if (isShifted) {
|
||||
_myCamera.setEyeOffsetOrientation(glm::normalize(
|
||||
glm::quat(glm::vec3(0, 0, 0.002f)) * _myCamera.getEyeOffsetOrientation()));
|
||||
} else {
|
||||
_myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, 0, 0.001));
|
||||
}
|
||||
updateProjectionMatrix();
|
||||
break;
|
||||
#endif
|
||||
|
||||
case Qt::Key_Slash:
|
||||
Menu::getInstance()->triggerOption(MenuOption::Stats);
|
||||
break;
|
||||
|
@ -4236,7 +4200,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;
|
||||
}
|
||||
|
||||
|
@ -4632,12 +4596,6 @@ void Application::idle() {
|
|||
|
||||
_overlayConductor.update(secondsSinceLastUpdate);
|
||||
|
||||
auto myAvatar = getMyAvatar();
|
||||
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON || _myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN);
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !(myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN));
|
||||
cameraMenuChanged();
|
||||
}
|
||||
_gameLoopCounter.increment();
|
||||
}
|
||||
|
||||
|
@ -4802,12 +4760,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())) {
|
||||
|
@ -5184,6 +5145,21 @@ void Application::cameraModeChanged() {
|
|||
cameraMenuChanged();
|
||||
}
|
||||
|
||||
void Application::changeViewAsNeeded(float boomLength) {
|
||||
// Switch between first and third person views as needed
|
||||
// This is called when the boom length has changed
|
||||
bool boomLengthGreaterThanMinimum = (boomLength > MyAvatar::ZOOM_MIN);
|
||||
|
||||
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON && boomLengthGreaterThanMinimum) {
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false);
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true);
|
||||
cameraMenuChanged();
|
||||
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON && !boomLengthGreaterThanMinimum) {
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, true);
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, false);
|
||||
cameraMenuChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::cameraMenuChanged() {
|
||||
auto menu = Menu::getInstance();
|
||||
|
@ -5462,7 +5438,7 @@ void Application::update(float deltaTime) {
|
|||
// process octree stats packets are sent in between full sends of a scene (this isn't currently true).
|
||||
// We keep physics disabled until we've received a full scene and everything near the avatar in that
|
||||
// scene is ready to compute its collision shape.
|
||||
if (nearbyEntitiesAreReadyForPhysics()) {
|
||||
if (nearbyEntitiesAreReadyForPhysics() && getMyAvatar()->isReadyForPhysics()) {
|
||||
_physicsEnabled = true;
|
||||
getMyAvatar()->updateMotionBehaviorFromMenu();
|
||||
}
|
||||
|
@ -5820,7 +5796,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();
|
||||
|
@ -6188,7 +6164,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)";
|
||||
|
||||
|
@ -7634,18 +7612,18 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa
|
|||
});
|
||||
}
|
||||
|
||||
void Application::takeSecondaryCameraSnapshot(const QString& filename) {
|
||||
postLambdaEvent([filename, this] {
|
||||
void Application::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) {
|
||||
postLambdaEvent([notify, filename, this] {
|
||||
QString snapshotPath = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename,
|
||||
TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, true);
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, notify);
|
||||
});
|
||||
}
|
||||
|
||||
void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename) {
|
||||
postLambdaEvent([filename, cubemapOutputFormat, cameraPosition] {
|
||||
DependencyManager::get<Snapshot>()->save360Snapshot(cameraPosition, cubemapOutputFormat, filename);
|
||||
void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) {
|
||||
postLambdaEvent([notify, filename, cubemapOutputFormat, cameraPosition] {
|
||||
DependencyManager::get<Snapshot>()->save360Snapshot(cameraPosition, cubemapOutputFormat, notify, filename);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -7749,7 +7727,7 @@ void Application::sendLambdaEvent(const std::function<void()>& f) {
|
|||
} else {
|
||||
LambdaEvent event(f);
|
||||
QCoreApplication::sendEvent(this, &event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::initPlugins(const QStringList& arguments) {
|
||||
|
@ -7972,7 +7950,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>();
|
||||
|
@ -8081,7 +8059,6 @@ void Application::switchDisplayMode() {
|
|||
setActiveDisplayPlugin(DESKTOP_DISPLAY_PLUGIN_NAME);
|
||||
startHMDStandBySession();
|
||||
}
|
||||
emit activeDisplayPluginChanged();
|
||||
}
|
||||
_previousHMDWornStatus = currentHMDWornStatus;
|
||||
}
|
||||
|
|
|
@ -281,8 +281,11 @@ public:
|
|||
float getGameLoopRate() const { return _gameLoopCounter.rate(); }
|
||||
|
||||
void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f, const QString& filename = QString());
|
||||
void takeSecondaryCameraSnapshot(const QString& filename = QString());
|
||||
void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename = QString());
|
||||
void takeSecondaryCameraSnapshot(const bool& notify, const QString& filename = QString());
|
||||
void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition,
|
||||
const bool& cubemapOutputFormat,
|
||||
const bool& notify,
|
||||
const QString& filename = QString());
|
||||
|
||||
void shareSnapshot(const QString& filename, const QUrl& href = QUrl(""));
|
||||
|
||||
|
@ -417,6 +420,8 @@ public slots:
|
|||
|
||||
void updateVerboseLogging();
|
||||
|
||||
void changeViewAsNeeded(float boomLength);
|
||||
|
||||
private slots:
|
||||
void onDesktopRootItemCreated(QQuickItem* qmlContext);
|
||||
void onDesktopRootContextCreated(QQmlContext* qmlContext);
|
||||
|
@ -649,7 +654,7 @@ private:
|
|||
quint64 _lastFaceTrackerUpdate;
|
||||
|
||||
render::ScenePointer _main3DScene{ new render::Scene(glm::vec3(-0.5f * (float)TREE_SCALE), (float)TREE_SCALE) };
|
||||
render::EnginePointer _renderEngine{ new render::Engine() };
|
||||
render::EnginePointer _renderEngine{ new render::RenderEngine() };
|
||||
gpu::ContextPointer _gpuContext; // initialized during window creation
|
||||
|
||||
mutable QMutex _renderArgsMutex{ QMutex::Recursive };
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class FancyCamera : public Camera {
|
|||
|
||||
// FIXME: JSDoc 3.5.5 doesn't augment @property definitions. The following definition is repeated in Camera.h.
|
||||
/**jsdoc
|
||||
* @property cameraEntity {Uuid} The ID of the entity that the camera position and orientation follow when the camera is in
|
||||
* @property {Uuid} cameraEntity The ID of the entity that the camera position and orientation follow when the camera is in
|
||||
* entity mode.
|
||||
*/
|
||||
Q_PROPERTY(QUuid cameraEntity READ getCameraEntity WRITE setCameraEntity)
|
||||
|
|
|
@ -103,16 +103,32 @@ Menu::Menu() {
|
|||
editMenu->addSeparator();
|
||||
|
||||
// Edit > Cut
|
||||
addActionToQMenuAndActionHash(editMenu, "Cut", Qt::CTRL | Qt::Key_X);
|
||||
auto cutAction = addActionToQMenuAndActionHash(editMenu, "Cut", QKeySequence::Cut);
|
||||
connect(cutAction, &QAction::triggered, [] {
|
||||
QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_X, Qt::ControlModifier);
|
||||
QCoreApplication::postEvent(QCoreApplication::instance(), keyEvent);
|
||||
});
|
||||
|
||||
// Edit > Copy
|
||||
addActionToQMenuAndActionHash(editMenu, "Copy", Qt::CTRL | Qt::Key_C);
|
||||
auto copyAction = addActionToQMenuAndActionHash(editMenu, "Copy", QKeySequence::Copy);
|
||||
connect(copyAction, &QAction::triggered, [] {
|
||||
QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier);
|
||||
QCoreApplication::postEvent(QCoreApplication::instance(), keyEvent);
|
||||
});
|
||||
|
||||
// Edit > Paste
|
||||
addActionToQMenuAndActionHash(editMenu, "Paste", Qt::CTRL | Qt::Key_V);
|
||||
auto pasteAction = addActionToQMenuAndActionHash(editMenu, "Paste", QKeySequence::Paste);
|
||||
connect(pasteAction, &QAction::triggered, [] {
|
||||
QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_V, Qt::ControlModifier);
|
||||
QCoreApplication::postEvent(QCoreApplication::instance(), keyEvent);
|
||||
});
|
||||
|
||||
// Edit > Delete
|
||||
addActionToQMenuAndActionHash(editMenu, "Delete", Qt::Key_Delete);
|
||||
auto deleteAction = addActionToQMenuAndActionHash(editMenu, "Delete", QKeySequence::Delete);
|
||||
connect(deleteAction, &QAction::triggered, [] {
|
||||
QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Delete, Qt::ControlModifier);
|
||||
QCoreApplication::postEvent(QCoreApplication::instance(), keyEvent);
|
||||
});
|
||||
|
||||
editMenu->addSeparator();
|
||||
|
||||
|
@ -201,21 +217,21 @@ Menu::Menu() {
|
|||
|
||||
// View > First Person
|
||||
auto firstPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
|
||||
viewMenu, MenuOption::FirstPerson, Qt::Key_1,
|
||||
viewMenu, MenuOption::FirstPerson, 0,
|
||||
true, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
firstPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
// View > Third Person
|
||||
auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
|
||||
viewMenu, MenuOption::ThirdPerson, Qt::Key_3,
|
||||
viewMenu, MenuOption::ThirdPerson, 0,
|
||||
false, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
thirdPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
// View > Mirror
|
||||
auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
|
||||
viewMenu, MenuOption::FullscreenMirror, Qt::Key_2,
|
||||
viewMenu, MenuOption::FullscreenMirror, 0,
|
||||
false, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
viewMirrorAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
@ -572,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,
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "Application.h"
|
||||
#include "SecondaryCamera.h"
|
||||
#include <TextureCache.h>
|
||||
#include <gpu/Context.h>
|
||||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
#include <gpu/Context.h>
|
||||
#include <TextureCache.h>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
using RenderArgsPointer = std::shared_ptr<RenderArgs>;
|
||||
|
||||
|
|
|
@ -104,6 +104,9 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
|
|||
PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()");
|
||||
|
||||
_myAvatar->update(deltaTime);
|
||||
render::Transaction transaction;
|
||||
_myAvatar->updateRenderItem(transaction);
|
||||
qApp->getMain3DScene()->enqueueTransaction(transaction);
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
quint64 dt = now - _lastSendAvatarDataTime;
|
||||
|
@ -465,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);
|
||||
}
|
||||
|
@ -511,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()))) {
|
||||
|
|
|
@ -21,17 +21,6 @@ AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisi
|
|||
_type = MOTIONSTATE_TYPE_AVATAR;
|
||||
}
|
||||
|
||||
void AvatarMotionState::handleEasyChanges(uint32_t& flags) {
|
||||
ObjectMotionState::handleEasyChanges(flags);
|
||||
if (flags & Simulation::DIRTY_PHYSICS_ACTIVATION && !_body->isActive()) {
|
||||
_body->activate();
|
||||
}
|
||||
}
|
||||
|
||||
bool AvatarMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
|
||||
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
|
||||
}
|
||||
|
||||
AvatarMotionState::~AvatarMotionState() {
|
||||
assert(_avatar);
|
||||
_avatar = nullptr;
|
||||
|
@ -57,9 +46,6 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const {
|
|||
const btCollisionShape* AvatarMotionState::computeNewShape() {
|
||||
ShapeInfo shapeInfo;
|
||||
std::static_pointer_cast<Avatar>(_avatar)->computeShapeInfo(shapeInfo);
|
||||
glm::vec3 halfExtents = shapeInfo.getHalfExtents();
|
||||
halfExtents.y = 0.0f;
|
||||
_diameter = 2.0f * glm::length(halfExtents);
|
||||
return getShapeManager()->getShape(shapeInfo);
|
||||
}
|
||||
|
||||
|
@ -74,31 +60,25 @@ void AvatarMotionState::getWorldTransform(btTransform& worldTrans) const {
|
|||
worldTrans.setRotation(glmToBullet(getObjectRotation()));
|
||||
if (_body) {
|
||||
_body->setLinearVelocity(glmToBullet(getObjectLinearVelocity()));
|
||||
_body->setAngularVelocity(glmToBullet(getObjectAngularVelocity()));
|
||||
_body->setAngularVelocity(glmToBullet(getObjectLinearVelocity()));
|
||||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void AvatarMotionState::setWorldTransform(const btTransform& worldTrans) {
|
||||
// HACK: The PhysicsEngine does not actually move OTHER avatars -- instead it slaves their local RigidBody to the transform
|
||||
// as specified by a remote simulation. However, to give the remote simulation time to respond to our own objects we tie
|
||||
// the other avatar's body to its true position with a simple spring. This is a HACK that will have to be improved later.
|
||||
const float SPRING_TIMESCALE = 0.5f;
|
||||
float tau = PHYSICS_ENGINE_FIXED_SUBSTEP / SPRING_TIMESCALE;
|
||||
btVector3 currentPosition = worldTrans.getOrigin();
|
||||
btVector3 offsetToTarget = glmToBullet(getObjectPosition()) - currentPosition;
|
||||
float distance = offsetToTarget.length();
|
||||
if ((1.0f - tau) * distance > _diameter) {
|
||||
// the avatar body is far from its target --> slam position
|
||||
btTransform newTransform;
|
||||
newTransform.setOrigin(currentPosition + offsetToTarget);
|
||||
newTransform.setRotation(glmToBullet(getObjectRotation()));
|
||||
_body->setWorldTransform(newTransform);
|
||||
_body->setLinearVelocity(glmToBullet(getObjectLinearVelocity()));
|
||||
_body->setAngularVelocity(glmToBullet(getObjectAngularVelocity()));
|
||||
} else {
|
||||
// the avatar body is near its target --> slam velocity
|
||||
btVector3 velocity = glmToBullet(getObjectLinearVelocity()) + (1.0f / SPRING_TIMESCALE) * offsetToTarget;
|
||||
_body->setLinearVelocity(velocity);
|
||||
_body->setAngularVelocity(glmToBullet(getObjectAngularVelocity()));
|
||||
}
|
||||
btVector3 targetPosition = glmToBullet(getObjectPosition());
|
||||
btTransform newTransform;
|
||||
newTransform.setOrigin((1.0f - tau) * currentPosition + tau * targetPosition);
|
||||
newTransform.setRotation(glmToBullet(getObjectRotation()));
|
||||
_body->setWorldTransform(newTransform);
|
||||
_body->setLinearVelocity(glmToBullet(getObjectLinearVelocity()));
|
||||
_body->setAngularVelocity(glmToBullet(getObjectLinearVelocity()));
|
||||
}
|
||||
|
||||
// These pure virtual methods must be implemented for each MotionState type
|
||||
|
@ -160,13 +140,8 @@ QUuid AvatarMotionState::getSimulatorID() const {
|
|||
}
|
||||
|
||||
// virtual
|
||||
void AvatarMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const {
|
||||
void AvatarMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const {
|
||||
group = BULLET_COLLISION_GROUP_OTHER_AVATAR;
|
||||
mask = Physics::getDefaultCollisionMask(group);
|
||||
}
|
||||
|
||||
// virtual
|
||||
float AvatarMotionState::getMass() const {
|
||||
return std::static_pointer_cast<Avatar>(_avatar)->computeMass();
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,6 @@ class AvatarMotionState : public ObjectMotionState {
|
|||
public:
|
||||
AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape);
|
||||
|
||||
virtual void handleEasyChanges(uint32_t& flags) override;
|
||||
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
|
||||
|
||||
virtual PhysicsMotionType getMotionType() const override { return _motionType; }
|
||||
|
||||
virtual uint32_t getIncomingDirtyFlags() override;
|
||||
|
@ -65,9 +62,7 @@ public:
|
|||
|
||||
void addDirtyFlags(uint32_t flags) { _dirtyFlags |= flags; }
|
||||
|
||||
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
|
||||
|
||||
virtual float getMass() const override;
|
||||
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
|
||||
|
||||
friend class AvatarManager;
|
||||
friend class Avatar;
|
||||
|
@ -81,7 +76,6 @@ protected:
|
|||
virtual const btCollisionShape* computeNewShape() override;
|
||||
|
||||
AvatarSharedPointer _avatar;
|
||||
float _diameter { 0.0f };
|
||||
|
||||
uint32_t _dirtyFlags;
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
@ -654,8 +655,8 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
if (success) {
|
||||
moveOperator.addEntityToMoveList(entity, newCube);
|
||||
}
|
||||
// send an edit packet to update the entity-server about the queryAABox. If it's an
|
||||
// avatar-entity, don't.
|
||||
// send an edit packet to update the entity-server about the queryAABox
|
||||
// unless it is client-only
|
||||
if (packetSender && !entity->getClientOnly()) {
|
||||
EntityItemProperties properties = entity->getProperties();
|
||||
properties.setQueryAACubeDirty();
|
||||
|
@ -663,6 +664,17 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties);
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
|
||||
entity->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||
EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant);
|
||||
if (!entityDescendant->getClientOnly() && descendant->updateQueryAACube()) {
|
||||
EntityItemProperties descendantProperties;
|
||||
descendantProperties.setQueryAACube(descendant->getQueryAACube());
|
||||
descendantProperties.setLastEdited(now);
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entityDescendant->getID(), descendantProperties);
|
||||
entityDescendant->setLastBroadcast(now); // for debug/physics status icons
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -701,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);
|
||||
}
|
||||
|
@ -1068,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;
|
||||
|
||||
|
@ -1127,7 +1156,11 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) {
|
|||
}
|
||||
|
||||
void MyAvatar::setEnableMeshVisible(bool isEnabled) {
|
||||
_skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
|
||||
return Avatar::setEnableMeshVisible(isEnabled);
|
||||
}
|
||||
|
||||
bool MyAvatar::getEnableMeshVisible() const {
|
||||
return Avatar::getEnableMeshVisible();
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
|
||||
|
@ -1195,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));
|
||||
|
@ -1479,7 +1514,10 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
_skeletonModelChangeCount++;
|
||||
int skeletonModelChangeCount = _skeletonModelChangeCount;
|
||||
Avatar::setSkeletonModelURL(skeletonModelURL);
|
||||
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
|
||||
_skeletonModel->setTagMask(render::hifi::TAG_NONE);
|
||||
_skeletonModel->setGroupCulled(true);
|
||||
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene());
|
||||
|
||||
_headBoneSet.clear();
|
||||
_cauterizationNeedsUpdate = true;
|
||||
|
||||
|
@ -2054,14 +2092,12 @@ void MyAvatar::preDisplaySide(const RenderArgs* renderArgs) {
|
|||
_attachmentData[i].jointName.compare("RightEye", Qt::CaseInsensitive) == 0 ||
|
||||
_attachmentData[i].jointName.compare("HeadTop_End", Qt::CaseInsensitive) == 0 ||
|
||||
_attachmentData[i].jointName.compare("Face", Qt::CaseInsensitive) == 0) {
|
||||
uint8_t modelRenderTagBits = shouldDrawHead ? render::ItemKey::TAG_BITS_0 : render::ItemKey::TAG_BITS_NONE;
|
||||
modelRenderTagBits |= render::ItemKey::TAG_BITS_1;
|
||||
_attachmentModels[i]->setVisibleInScene(true, qApp->getMain3DScene(),
|
||||
modelRenderTagBits, false);
|
||||
uint8_t modelRenderTagBits = shouldDrawHead ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_SECONDARY_VIEW;
|
||||
|
||||
uint8_t castShadowRenderTagBits = render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1;
|
||||
_attachmentModels[i]->setCanCastShadow(true, qApp->getMain3DScene(),
|
||||
castShadowRenderTagBits, false);
|
||||
_attachmentModels[i]->setTagMask(modelRenderTagBits);
|
||||
_attachmentModels[i]->setGroupCulled(false);
|
||||
_attachmentModels[i]->setCanCastShadow(true);
|
||||
_attachmentModels[i]->setVisibleInScene(true, qApp->getMain3DScene());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2081,6 +2117,31 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const {
|
|||
return !defaultMode || !firstPerson || !insideHead;
|
||||
}
|
||||
|
||||
void MyAvatar::setHasScriptedBlendshapes(bool hasScriptedBlendshapes) {
|
||||
if (hasScriptedBlendshapes == _hasScriptedBlendShapes) {
|
||||
return;
|
||||
}
|
||||
if (!hasScriptedBlendshapes) {
|
||||
// send a forced avatarData update to make sure the script can send neutal blendshapes on unload
|
||||
// without having to wait for the update loop, make sure _hasScriptedBlendShapes is still true
|
||||
// before sending the update, or else it won't send the neutal blendshapes to the receiving clients
|
||||
sendAvatarDataPacket(true);
|
||||
}
|
||||
_hasScriptedBlendShapes = hasScriptedBlendshapes;
|
||||
}
|
||||
|
||||
void MyAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) {
|
||||
_headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement);
|
||||
}
|
||||
|
||||
void MyAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) {
|
||||
_headData->setHasProceduralEyeFaceMovement(hasProceduralEyeFaceMovement);
|
||||
}
|
||||
|
||||
void MyAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) {
|
||||
_headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement);
|
||||
}
|
||||
|
||||
void MyAvatar::updateOrientation(float deltaTime) {
|
||||
|
||||
// Smoothly rotate body with arrow keys
|
||||
|
@ -2245,9 +2306,15 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
_actionMotorVelocity = getSensorToWorldScale() * (_walkSpeed.get() * _walkSpeedScalar) * direction;
|
||||
}
|
||||
|
||||
float previousBoomLength = _boomLength;
|
||||
float boomChange = getDriveKey(ZOOM);
|
||||
_boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange;
|
||||
_boomLength = glm::clamp<float>(_boomLength, ZOOM_MIN, ZOOM_MAX);
|
||||
|
||||
// May need to change view if boom length has changed
|
||||
if (previousBoomLength != _boomLength) {
|
||||
qApp->changeViewAsNeeded(_boomLength);
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::updatePosition(float deltaTime) {
|
||||
|
@ -2388,11 +2455,16 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings
|
|||
if (_domainMinimumHeight > _domainMaximumHeight) {
|
||||
std::swap(_domainMinimumHeight, _domainMaximumHeight);
|
||||
}
|
||||
|
||||
// Set avatar current scale
|
||||
Settings settings;
|
||||
settings.beginGroup("Avatar");
|
||||
_targetScale = loadSetting(settings, "scale", 1.0f);
|
||||
|
||||
// clamp the desired _targetScale by the domain limits NOW, don't try to gracefully animate. Because
|
||||
// this might cause our avatar to become embedded in the terrain.
|
||||
_targetScale = getDomainLimitedScale();
|
||||
|
||||
qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumHeight
|
||||
<< " and a maximum avatar scale of " << _domainMaximumHeight;
|
||||
|
||||
|
@ -2401,6 +2473,8 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings
|
|||
setModelScale(_targetScale);
|
||||
rebuildCollisionShape();
|
||||
settings.endGroup();
|
||||
|
||||
_haveReceivedHeightLimitsFromDomain = true;
|
||||
}
|
||||
|
||||
void MyAvatar::leaveDomain() {
|
||||
|
@ -2418,6 +2492,7 @@ void MyAvatar::saveAvatarScale() {
|
|||
void MyAvatar::clearScaleRestriction() {
|
||||
_domainMinimumHeight = MIN_AVATAR_HEIGHT;
|
||||
_domainMaximumHeight = MAX_AVATAR_HEIGHT;
|
||||
_haveReceivedHeightLimitsFromDomain = false;
|
||||
}
|
||||
|
||||
void MyAvatar::goToLocation(const QVariant& propertiesVar) {
|
||||
|
@ -2535,8 +2610,12 @@ bool MyAvatar::safeLanding(const glm::vec3& position) {
|
|||
|
||||
// If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut.
|
||||
bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) {
|
||||
|
||||
// We begin with utilities and tests. The Algorithm in four parts is below.
|
||||
auto halfHeight = _characterController.getCapsuleHalfHeight() + _characterController.getCapsuleRadius();
|
||||
// NOTE: we use estimated avatar height here instead of the bullet capsule halfHeight, because
|
||||
// the domain avatar height limiting might not have taken effect yet on the actual bullet shape.
|
||||
auto halfHeight = 0.5f * getHeight();
|
||||
|
||||
if (halfHeight == 0) {
|
||||
return false; // zero height avatar
|
||||
}
|
||||
|
@ -2545,14 +2624,13 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette
|
|||
return false; // no entity tree
|
||||
}
|
||||
// More utilities.
|
||||
const auto offset = getWorldOrientation() *_characterController.getCapsuleLocalOffset();
|
||||
const auto capsuleCenter = positionIn + offset;
|
||||
const auto capsuleCenter = positionIn;
|
||||
const auto up = _worldUpDirection, down = -up;
|
||||
glm::vec3 upperIntersection, upperNormal, lowerIntersection, lowerNormal;
|
||||
EntityItemID upperId, lowerId;
|
||||
QVector<EntityItemID> include{}, ignore{};
|
||||
auto mustMove = [&] { // Place bottom of capsule at the upperIntersection, and check again based on the capsule center.
|
||||
betterPositionOut = upperIntersection + (up * halfHeight) - offset;
|
||||
betterPositionOut = upperIntersection + (up * halfHeight);
|
||||
return true;
|
||||
};
|
||||
auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) {
|
||||
|
@ -2572,7 +2650,7 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette
|
|||
EntityItemID entityID = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking,
|
||||
element, distance, face, normalOut, extraInfo, lockType, accurateResult);
|
||||
if (entityID.isNull()) {
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
intersectionOut = startPointIn + (directionIn * distance);
|
||||
entityIdOut = entityID;
|
||||
|
@ -2598,7 +2676,7 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette
|
|||
// I.e., we are in a clearing between two objects.
|
||||
if (isDown(upperNormal) && isUp(lowerNormal)) {
|
||||
auto spaceBetween = glm::distance(upperIntersection, lowerIntersection);
|
||||
const float halfHeightFactor = 2.5f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise.
|
||||
const float halfHeightFactor = 2.25f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise.
|
||||
if (spaceBetween > (halfHeightFactor * halfHeight)) {
|
||||
// There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below.
|
||||
// We're now going to iterate upwards through successive upperIntersections, testing to see if we're contained within the top surface of some entity.
|
||||
|
@ -2797,6 +2875,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);
|
||||
|
@ -2819,6 +2898,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;
|
||||
|
||||
|
@ -2828,6 +2909,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();
|
||||
}
|
||||
|
@ -2849,6 +3126,10 @@ float MyAvatar::getWalkSpeed() const {
|
|||
return _walkSpeed.get() * _walkSpeedScalar;
|
||||
}
|
||||
|
||||
bool MyAvatar::isReadyForPhysics() const {
|
||||
return qApp->isServerlessMode() || _haveReceivedHeightLimitsFromDomain;
|
||||
}
|
||||
|
||||
void MyAvatar::setSprintMode(bool sprint) {
|
||||
_walkSpeedScalar = sprint ? AVATAR_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR;
|
||||
}
|
||||
|
@ -2992,9 +3273,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 {
|
||||
|
@ -3065,11 +3344,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);
|
||||
|
@ -3465,6 +3752,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());
|
||||
}
|
||||
|
|
|
@ -86,6 +86,10 @@ class MyAvatar : public Avatar {
|
|||
* @property {number} audioListenerModeCamera=1 - The audio listening position is at the camera. <em>Read-only.</em>
|
||||
* @property {number} audioListenerModeCustom=2 - The audio listening position is at a the position specified by set by the
|
||||
* <code>customListenPosition</code> and <code>customListenOrientation</code> property values. <em>Read-only.</em>
|
||||
* @property {boolean} hasScriptedBlendshapes=false - Blendshapes will be transmitted over the network if set to true.
|
||||
* @property {boolean} hasProceduralBlinkFaceMovement=true - procedural blinking will be turned on if set to true.
|
||||
* @property {boolean} hasProceduralEyeFaceMovement=true - procedural eye movement will be turned on if set to true.
|
||||
* @property {boolean} hasAudioEnabledFaceMovement=true - If set to true, voice audio will move the mouth Blendshapes while MyAvatar.hasScriptedBlendshapes is enabled.
|
||||
* @property {Vec3} customListenPosition=Vec3.ZERO - The listening position used when the <code>audioListenerMode</code>
|
||||
* property value is <code>audioListenerModeCustom</code>.
|
||||
* @property {Quat} customListenOrientation=Quat.IDENTITY - The listening orientation used when the
|
||||
|
@ -105,6 +109,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.
|
||||
|
@ -121,7 +128,7 @@ class MyAvatar : public Avatar {
|
|||
* while flying.
|
||||
* @property {number} hmdRollControlDeadZone=8 - The amount of HMD roll, in degrees, required before your avatar turns if
|
||||
* <code>hmdRollControlEnabled</code> is enabled.
|
||||
* @property hmdRollControlRate {number} If hmdRollControlEnabled is true, this value determines the maximum turn rate of
|
||||
* @property {number} hmdRollControlRate If hmdRollControlEnabled is true, this value determines the maximum turn rate of
|
||||
* your avatar when rolling your HMD in degrees per second.
|
||||
* @property {number} userHeight=1.75 - The height of the user in sensor space.
|
||||
* @property {number} userEyeHeight=1.65 - The estimated height of the user's eyes in sensor space. <em>Read-only.</em>
|
||||
|
@ -184,6 +191,10 @@ class MyAvatar : public Avatar {
|
|||
Q_PROPERTY(AudioListenerMode audioListenerModeHead READ getAudioListenerModeHead)
|
||||
Q_PROPERTY(AudioListenerMode audioListenerModeCamera READ getAudioListenerModeCamera)
|
||||
Q_PROPERTY(AudioListenerMode audioListenerModeCustom READ getAudioListenerModeCustom)
|
||||
Q_PROPERTY(bool hasScriptedBlendshapes READ getHasScriptedBlendshapes WRITE setHasScriptedBlendshapes)
|
||||
Q_PROPERTY(bool hasProceduralBlinkFaceMovement READ getHasProceduralBlinkFaceMovement WRITE setHasProceduralBlinkFaceMovement)
|
||||
Q_PROPERTY(bool hasProceduralEyeFaceMovement READ getHasProceduralEyeFaceMovement WRITE setHasProceduralEyeFaceMovement)
|
||||
Q_PROPERTY(bool hasAudioEnabledFaceMovement READ getHasAudioEnabledFaceMovement WRITE setHasAudioEnabledFaceMovement)
|
||||
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
|
||||
|
||||
Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition)
|
||||
|
@ -199,6 +210,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)
|
||||
|
@ -464,7 +476,7 @@ public:
|
|||
Q_INVOKABLE bool getClearOverlayWhenMoving() const { return _clearOverlayWhenMoving; }
|
||||
/**jsdoc
|
||||
* @function MyAvatar.setClearOverlayWhenMoving
|
||||
* @returns {boolean}
|
||||
* @param {boolean} on
|
||||
*/
|
||||
Q_INVOKABLE void setClearOverlayWhenMoving(bool on) { _clearOverlayWhenMoving = on; }
|
||||
|
||||
|
@ -480,7 +492,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 +585,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 +984,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
|
||||
|
@ -987,6 +1023,8 @@ public:
|
|||
|
||||
QVector<QString> getScriptUrls();
|
||||
|
||||
bool isReadyForPhysics() const;
|
||||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
|
@ -1107,7 +1145,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
|
||||
|
@ -1159,7 +1206,7 @@ public slots:
|
|||
* @function MyAvatar.getEnableMeshVisible
|
||||
* @returns {boolean} <code>true</code> if your avatar's mesh is visible, otherwise <code>false</code>.
|
||||
*/
|
||||
bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); }
|
||||
bool getEnableMeshVisible() const override;
|
||||
|
||||
/**jsdoc
|
||||
* Set whether or not your avatar mesh is visible.
|
||||
|
@ -1171,7 +1218,7 @@ public slots:
|
|||
* MyAvatar.setEnableMeshVisible(true);
|
||||
* }, 10000);
|
||||
*/
|
||||
void setEnableMeshVisible(bool isEnabled);
|
||||
virtual void setEnableMeshVisible(bool isEnabled) override;
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.setEnableInverseKinematics
|
||||
|
@ -1341,6 +1388,14 @@ private:
|
|||
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override;
|
||||
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; setEnableMeshVisible(shouldRender); }
|
||||
bool getShouldRenderLocally() const { return _shouldRender; }
|
||||
void setHasScriptedBlendshapes(bool hasScriptedBlendshapes);
|
||||
bool getHasScriptedBlendshapes() const override { return _hasScriptedBlendShapes; }
|
||||
void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement);
|
||||
bool getHasProceduralBlinkFaceMovement() const override { return _headData->getHasProceduralBlinkFaceMovement(); }
|
||||
void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement);
|
||||
bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); }
|
||||
void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement);
|
||||
bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); }
|
||||
bool isMyAvatar() const override { return true; }
|
||||
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
|
||||
virtual glm::vec3 getSkeletonPosition() const override;
|
||||
|
@ -1402,7 +1457,7 @@ private:
|
|||
SharedSoundPointer _collisionSound;
|
||||
|
||||
MyCharacterController _characterController;
|
||||
int16_t _previousCollisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR };
|
||||
int32_t _previousCollisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR };
|
||||
|
||||
AvatarWeakPointer _lookAtTargetAvatar;
|
||||
glm::vec3 _targetAvatarPosition;
|
||||
|
@ -1449,6 +1504,7 @@ private:
|
|||
bool _hmdRollControlEnabled { true };
|
||||
float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT };
|
||||
float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT };
|
||||
std::atomic<bool> _hasScriptedBlendShapes { false };
|
||||
|
||||
// working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access
|
||||
glm::mat4 _sensorToWorldMatrix { glm::mat4() };
|
||||
|
@ -1458,8 +1514,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 +1551,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 +1569,7 @@ private:
|
|||
bool _prevShouldDrawHead;
|
||||
bool _rigEnabled { true };
|
||||
|
||||
bool _enableDebugDrawBaseOfSupport { false };
|
||||
bool _enableDebugDrawDefaultPose { false };
|
||||
bool _enableDebugDrawAnimPose { false };
|
||||
bool _enableDebugDrawHandControllers { false };
|
||||
|
@ -1532,6 +1592,7 @@ private:
|
|||
std::map<controller::Action, controller::Pose> _controllerPoseMap;
|
||||
mutable std::mutex _controllerPoseMapMutex;
|
||||
|
||||
bool _centerOfGravityModelEnabled { true };
|
||||
bool _hmdLeanRecenterEnabled { true };
|
||||
bool _sprint { false };
|
||||
|
||||
|
@ -1568,6 +1629,8 @@ private:
|
|||
|
||||
// load avatar scripts once when rig is ready
|
||||
bool _shouldLoadScripts { false };
|
||||
|
||||
bool _haveReceivedHeightLimitsFromDomain = { false };
|
||||
};
|
||||
|
||||
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
|
||||
|
|
|
@ -46,32 +46,18 @@ void MyHead::simulate(float deltaTime) {
|
|||
auto player = DependencyManager::get<recording::Deck>();
|
||||
// Only use face trackers when not playing back a recording.
|
||||
if (!player->isPlaying()) {
|
||||
FaceTracker* faceTracker = qApp->getActiveFaceTracker();
|
||||
_isFaceTrackerConnected = faceTracker != nullptr && !faceTracker->isMuted();
|
||||
auto faceTracker = qApp->getActiveFaceTracker();
|
||||
const bool hasActualFaceTrackerConnected = faceTracker && !faceTracker->isMuted();
|
||||
_isFaceTrackerConnected = hasActualFaceTrackerConnected || _owningAvatar->getHasScriptedBlendshapes();
|
||||
if (_isFaceTrackerConnected) {
|
||||
_transientBlendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
|
||||
|
||||
if (typeid(*faceTracker) == typeid(DdeFaceTracker)) {
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::UseAudioForMouth)) {
|
||||
calculateMouthShapes(deltaTime);
|
||||
|
||||
const int JAW_OPEN_BLENDSHAPE = 21;
|
||||
const int MMMM_BLENDSHAPE = 34;
|
||||
const int FUNNEL_BLENDSHAPE = 40;
|
||||
const int SMILE_LEFT_BLENDSHAPE = 28;
|
||||
const int SMILE_RIGHT_BLENDSHAPE = 29;
|
||||
_transientBlendshapeCoefficients[JAW_OPEN_BLENDSHAPE] += _audioJawOpen;
|
||||
_transientBlendshapeCoefficients[SMILE_LEFT_BLENDSHAPE] += _mouth4;
|
||||
_transientBlendshapeCoefficients[SMILE_RIGHT_BLENDSHAPE] += _mouth4;
|
||||
_transientBlendshapeCoefficients[MMMM_BLENDSHAPE] += _mouth2;
|
||||
_transientBlendshapeCoefficients[FUNNEL_BLENDSHAPE] += _mouth3;
|
||||
}
|
||||
applyEyelidOffset(getFinalOrientationInWorldFrame());
|
||||
if (hasActualFaceTrackerConnected) {
|
||||
_blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
|
||||
}
|
||||
}
|
||||
|
||||
auto eyeTracker = DependencyManager::get<EyeTracker>();
|
||||
_isEyeTrackerConnected = eyeTracker->isTracking();
|
||||
// if eye tracker is connected we should get the data here.
|
||||
}
|
||||
Parent::simulate(deltaTime);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -28,6 +28,8 @@ private:
|
|||
|
||||
AnimPose _prevHips; // sensor frame
|
||||
bool _prevHipsValid { false };
|
||||
bool _prevIsFlying { false };
|
||||
float _flyIdleTimer { 0.0f };
|
||||
|
||||
std::map<int, int> _jointRotationFrameOffsetMap;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,10 +227,13 @@ QString QmlCommerce::getInstalledApps() {
|
|||
QString scriptURL = appFileJsonObject["scriptURL"].toString();
|
||||
|
||||
// If the script .app.json is on the user's local disk but the associated script isn't running
|
||||
// for some reason, start that script again.
|
||||
// for some reason (i.e. the user stopped it from Running Scripts),
|
||||
// delete the .app.json from the user's local disk.
|
||||
if (!runningScripts.contains(scriptURL)) {
|
||||
if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptURL.trimmed())).isNull()) {
|
||||
qCDebug(commerce) << "Couldn't start script while checking installed apps.";
|
||||
if (!appFile.remove()) {
|
||||
qCWarning(commerce)
|
||||
<< "Couldn't delete local .app.json file (app's script isn't running). App filename is:"
|
||||
<< appFileName;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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(""));
|
||||
|
|
|
@ -81,6 +81,13 @@ int main(int argc, const char* argv[]) {
|
|||
|
||||
// Instance UserActivityLogger now that the settings are loaded
|
||||
auto& ual = UserActivityLogger::getInstance();
|
||||
// once the settings have been loaded, check if we need to flip the default for UserActivityLogger
|
||||
if (!ual.isDisabledSettingSet()) {
|
||||
// the user activity logger is opt-out for Interface
|
||||
// but it's defaulted to disabled for other targets
|
||||
// so we need to enable it here if it has never been disabled by the user
|
||||
ual.disable(false);
|
||||
}
|
||||
qDebug() << "UserActivityLogger is enabled:" << ual.isEnabled();
|
||||
|
||||
if (ual.isEnabled()) {
|
||||
|
|
|
@ -36,7 +36,7 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type,
|
|||
* @typedef {object} Picks.RayPickProperties
|
||||
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
|
||||
* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {float} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
|
||||
* @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
|
||||
* @property {string} [joint] Only for Joint or Mouse Ray Picks. If "Mouse", it will create a Ray Pick that follows the system mouse, in desktop or HMD.
|
||||
* If "Avatar", it will create a Joint Ray Pick that follows your avatar's head. Otherwise, it will create a Joint Ray Pick that follows the given joint, if it
|
||||
* exists on your current avatar.
|
||||
|
@ -103,7 +103,7 @@ unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) {
|
|||
* @property {number} [hand=-1] An integer. 0 == left, 1 == right. Invalid otherwise.
|
||||
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
|
||||
* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {float} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
|
||||
* @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
|
||||
*/
|
||||
unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties) {
|
||||
QVariantMap propMap = properties.toMap();
|
||||
|
|
|
@ -22,21 +22,22 @@
|
|||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
*
|
||||
* @property PICK_NOTHING {number} A filter flag. Don't intersect with anything. <em>Read-only.</em>
|
||||
* @property PICK_ENTITIES {number} A filter flag. Include entities when intersecting. <em>Read-only.</em>
|
||||
* @property PICK_OVERLAYS {number} A filter flag. Include overlays when intersecting. <em>Read-only.</em>
|
||||
* @property PICK_AVATARS {number} A filter flag. Include avatars when intersecting. <em>Read-only.</em>
|
||||
* @property PICK_HUD {number} A filter flag. Include the HUD sphere when intersecting in HMD mode. <em>Read-only.</em>
|
||||
* @property PICK_COARSE {number} A filter flag. Pick against coarse meshes, instead of exact meshes. <em>Read-only.</em>
|
||||
* @property PICK_INCLUDE_INVISIBLE {number} A filter flag. Include invisible objects when intersecting. <em>Read-only.</em>
|
||||
* @property PICK_INCLUDE_NONCOLLIDABLE {number} A filter flag. Include non-collidable objects when intersecting.
|
||||
* @property {number} PICK_NOTHING A filter flag. Don't intersect with anything. <em>Read-only.</em>
|
||||
* @property {number} PICK_ENTITIES A filter flag. Include entities when intersecting. <em>Read-only.</em>
|
||||
* @property {number} PICK_OVERLAYS A filter flag. Include overlays when intersecting. <em>Read-only.</em>
|
||||
* @property {number} PICK_AVATARS A filter flag. Include avatars when intersecting. <em>Read-only.</em>
|
||||
* @property {number} PICK_HUD A filter flag. Include the HUD sphere when intersecting in HMD mode. <em>Read-only.</em>
|
||||
* @property {number} PICK_COARSE A filter flag. Pick against coarse meshes, instead of exact meshes. <em>Read-only.</em>
|
||||
* @property {number} PICK_INCLUDE_INVISIBLE A filter flag. Include invisible objects when intersecting. <em>Read-only.</em>
|
||||
* @property {number} PICK_INCLUDE_NONCOLLIDABLE A filter flag. Include non-collidable objects when intersecting.
|
||||
* <em>Read-only.</em>
|
||||
* @property PICK_ALL_INTERSECTIONS {number} <em>Read-only.</em>
|
||||
* @property INTERSECTED_NONE {number} An intersection type. Intersected nothing with the given filter flags. <em>Read-only.</em>
|
||||
* @property INTERSECTED_ENTITY {number} An intersection type. Intersected an entity. <em>Read-only.</em>
|
||||
* @property INTERSECTED_OVERLAY {number} An intersection type. Intersected an overlay. <em>Read-only.</em>
|
||||
* @property INTERSECTED_AVATAR {number} An intersection type. Intersected an avatar. <em>Read-only.</em>
|
||||
* @property INTERSECTED_HUD {number} An intersection type. Intersected the HUD sphere. <em>Read-only.</em>
|
||||
* @property {number} PICK_ALL_INTERSECTIONS <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_NONE An intersection type. Intersected nothing with the given filter flags.
|
||||
* <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_ENTITY An intersection type. Intersected an entity. <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_OVERLAY An intersection type. Intersected an overlay. <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_AVATAR An intersection type. Intersected an avatar. <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_HUD An intersection type. Intersected the HUD sphere. <em>Read-only.</em>
|
||||
* @property {number} perFrameTimeBudget - The max number of usec to spend per frame updating Pick results. <em>Read-only.</em>
|
||||
*/
|
||||
|
||||
|
@ -99,11 +100,11 @@ public:
|
|||
/**jsdoc
|
||||
* An intersection result for a Ray Pick.
|
||||
*
|
||||
* @typedef {Object} RayPickResult
|
||||
* @typedef {object} RayPickResult
|
||||
* @property {number} type The intersection type.
|
||||
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
|
||||
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
|
||||
* @property {float} distance The distance to the intersection point from the origin of the ray.
|
||||
* @property {number} distance The distance to the intersection point from the origin of the ray.
|
||||
* @property {Vec3} intersection The intersection point in world-space.
|
||||
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
|
||||
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
|
||||
|
@ -113,11 +114,11 @@ public:
|
|||
/**jsdoc
|
||||
* An intersection result for a Stylus Pick.
|
||||
*
|
||||
* @typedef {Object} StylusPickResult
|
||||
* @typedef {object} StylusPickResult
|
||||
* @property {number} type The intersection type.
|
||||
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
|
||||
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
|
||||
* @property {float} distance The distance to the intersection point from the origin of the ray.
|
||||
* @property {number} distance The distance to the intersection point from the origin of the ray.
|
||||
* @property {Vec3} intersection The intersection point in world-space.
|
||||
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
|
||||
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
|
||||
|
|
|
@ -68,14 +68,14 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties)
|
|||
* A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.RayPointerRenderState},
|
||||
* but with an additional distance field.
|
||||
*
|
||||
* @typedef {Object} Pointers.DefaultRayPointerRenderState
|
||||
* @typedef {object} Pointers.DefaultRayPointerRenderState
|
||||
* @augments Pointers.RayPointerRenderState
|
||||
* @property {number} distance The distance at which to render the end of this Ray Pointer, if one is defined.
|
||||
*/
|
||||
/**jsdoc
|
||||
* A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something.
|
||||
*
|
||||
* @typedef {Object} Pointers.RayPointerRenderState
|
||||
* @typedef {object} Pointers.RayPointerRenderState
|
||||
* @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState}
|
||||
* @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field).
|
||||
* An overlay to represent the beginning of the Ray Pointer, if desired.
|
||||
|
@ -87,7 +87,7 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties)
|
|||
/**jsdoc
|
||||
* A trigger mechanism for Ray Pointers.
|
||||
*
|
||||
* @typedef {Object} Pointers.Trigger
|
||||
* @typedef {object} Pointers.Trigger
|
||||
* @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger <code>button</code>.
|
||||
* @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
|
||||
* it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".
|
||||
|
|
|
@ -136,7 +136,7 @@ public:
|
|||
* Sets the length of this Pointer. No effect on Stylus Pointers.
|
||||
* @function Pointers.setLength
|
||||
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
|
||||
* @param {float} length The desired length of the Pointer.
|
||||
* @param {number} length The desired length of the Pointer.
|
||||
*/
|
||||
Q_INVOKABLE void setLength(unsigned int uid, float length) const { DependencyManager::get<PointerManager>()->setLength(uid, length); }
|
||||
|
||||
|
|
|
@ -105,9 +105,6 @@ class ScriptEngine;
|
|||
* <li>{@link Controller.getValue|getValue}</li>
|
||||
* <li>{@link Controller.getAxisValue|getAxisValue}</li>
|
||||
* <li>{@link Controller.getPoseValue|getgetPoseValue}</li>
|
||||
* <li>{@link Controller.getButtonValue|getButtonValue} for a particular device</li>
|
||||
* <li>{@link Controller.getAxisValue(0)|getAxisValue} for a particular device</li>
|
||||
* <li>{@link Controller.getPoseValue(0)|getPoseValue} for a particular device</li>
|
||||
* <li>{@link Controller.getActionValue|getActionValue}</li>
|
||||
* </ul>
|
||||
*
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <shared/FileUtils.h>
|
||||
#include <shared/QtHelpers.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <MainWindow.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <StatTracker.h>
|
||||
#include <Trace.h>
|
||||
|
@ -186,3 +187,7 @@ void TestScriptingInterface::saveObject(QVariant variant, const QString& filenam
|
|||
file.write(jsonData);
|
||||
file.close();
|
||||
}
|
||||
|
||||
void TestScriptingInterface::showMaximized() {
|
||||
qApp->getWindow()->showMaximized();
|
||||
}
|
|
@ -27,70 +27,128 @@ public slots:
|
|||
|
||||
/**jsdoc
|
||||
* Exits the application
|
||||
* @function Test.quit
|
||||
*/
|
||||
void quit();
|
||||
|
||||
/**jsdoc
|
||||
* Waits for all texture transfers to be complete
|
||||
* @function Test.waitForTextureIdle
|
||||
*/
|
||||
void waitForTextureIdle();
|
||||
|
||||
/**jsdoc
|
||||
* Waits for all pending downloads to be complete
|
||||
* @function Test.waitForDownloadIdle
|
||||
*/
|
||||
void waitForDownloadIdle();
|
||||
|
||||
/**jsdoc
|
||||
* Waits for all file parsing operations to be complete
|
||||
* @function Test.waitForProcessingIdle
|
||||
*/
|
||||
void waitForProcessingIdle();
|
||||
|
||||
/**jsdoc
|
||||
* Waits for all pending downloads, parsing and texture transfers to be complete
|
||||
* @function Test.waitIdle
|
||||
*/
|
||||
void waitIdle();
|
||||
|
||||
/**jsdoc
|
||||
* Waits for establishment of connection to server
|
||||
* @function Test.waitForConnection
|
||||
* @param {int} maxWaitMs [default=10000] - Number of milliseconds to wait
|
||||
*/
|
||||
bool waitForConnection(qint64 maxWaitMs = 10000);
|
||||
|
||||
/**jsdoc
|
||||
* Waits a specific number of milliseconds
|
||||
* @function Test.wait
|
||||
* @param {int} milliseconds - Number of milliseconds to wait
|
||||
*/
|
||||
void wait(int milliseconds);
|
||||
|
||||
/**jsdoc
|
||||
* Waits for all pending downloads, parsing and texture transfers to be complete
|
||||
* @function Test.loadTestScene
|
||||
* @param {string} sceneFile - URL of scene to load
|
||||
*/
|
||||
bool loadTestScene(QString sceneFile);
|
||||
|
||||
/**jsdoc
|
||||
* Clears all caches
|
||||
* @function Test.clear
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**jsdoc
|
||||
* Start recording Chrome compatible tracing events
|
||||
* logRules can be used to specify a set of logging category rules to limit what gets captured
|
||||
* @function Test.startTracing
|
||||
* @param {string} logrules [defaultValue=""] - See implementation for explanation
|
||||
*/
|
||||
bool startTracing(QString logrules = "");
|
||||
|
||||
/**jsdoc
|
||||
* Stop recording Chrome compatible tracing events and serialize recorded events to a file
|
||||
* Using a filename with a .gz extension will automatically compress the output file
|
||||
* @function Test.stopTracing
|
||||
* @param {string} filename - Name of file to save to
|
||||
* @returns {bool} True if successful.
|
||||
*/
|
||||
bool stopTracing(QString filename);
|
||||
|
||||
/**jsdoc
|
||||
* Starts a specific trace event
|
||||
* @function Test.startTraceEvent
|
||||
* @param {string} name - Name of event
|
||||
*/
|
||||
void startTraceEvent(QString name);
|
||||
|
||||
/**jsdoc
|
||||
* Stop a specific name event
|
||||
* Using a filename with a .gz extension will automatically compress the output file
|
||||
* @function Test.endTraceEvent
|
||||
* @param {string} filename - Name of event
|
||||
*/
|
||||
void endTraceEvent(QString name);
|
||||
|
||||
/**jsdoc
|
||||
* Write detailed timing stats of next physics stepSimulation() to filename
|
||||
* @function Test.savePhysicsSimulationStats
|
||||
* @param {string} filename - Name of file to save to
|
||||
*/
|
||||
void savePhysicsSimulationStats(QString filename);
|
||||
|
||||
/**jsdoc
|
||||
* Profiles a specific function
|
||||
* @function Test.savePhysicsSimulationStats
|
||||
* @param {string} name - Name used to reference the function
|
||||
* @param {function} function - Function to profile
|
||||
*/
|
||||
Q_INVOKABLE void profileRange(const QString& name, QScriptValue function);
|
||||
|
||||
/**jsdoc
|
||||
* Clear all caches (menu command Reload Content)
|
||||
* @function Test.clearCaches
|
||||
*/
|
||||
void clearCaches();
|
||||
|
||||
/**jsdoc
|
||||
* Save a JSON object to a file in the test results location
|
||||
* @function Test.saveObject
|
||||
* @param {string} name - Name of the object
|
||||
* @param {string} filename - Name of file to save to
|
||||
*/
|
||||
void saveObject(QVariant v, const QString& filename);
|
||||
|
||||
/**jsdoc
|
||||
* Maximizes the window
|
||||
* @function Test.showMaximized
|
||||
*/
|
||||
void showMaximized();
|
||||
|
||||
private:
|
||||
bool waitForCondition(qint64 maxWaitMs, std::function<bool()> condition);
|
||||
QString _testResultsLocation;
|
||||
|
|
|
@ -446,12 +446,12 @@ void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, f
|
|||
qApp->takeSnapshot(notify, includeAnimated, aspectRatio, filename);
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::takeSecondaryCameraSnapshot(const QString& filename) {
|
||||
qApp->takeSecondaryCameraSnapshot(filename);
|
||||
void WindowScriptingInterface::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) {
|
||||
qApp->takeSecondaryCameraSnapshot(notify, filename);
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename) {
|
||||
qApp->takeSecondaryCamera360Snapshot(cameraPosition, cubemapOutputFormat, filename);
|
||||
void WindowScriptingInterface::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) {
|
||||
qApp->takeSecondaryCamera360Snapshot(cameraPosition, cubemapOutputFormat, notify, filename);
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::shareSnapshot(const QString& path, const QUrl& href) {
|
||||
|
@ -522,7 +522,7 @@ int WindowScriptingInterface::openMessageBox(QString title, QString text, int bu
|
|||
* <tr> <td><strong>RestoreDefaults</strong></td> <td><code>0x8000000</code></td> <td>"Restore Defaults"</td> </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @typedef Window.MessageBoxButton
|
||||
* @typedef {number} Window.MessageBoxButton
|
||||
*/
|
||||
int WindowScriptingInterface::createMessageBox(QString title, QString text, int buttons, int defaultButton) {
|
||||
auto messageBox = DependencyManager::get<OffscreenUi>()->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text,
|
||||
|
|
|
@ -318,7 +318,6 @@ public slots:
|
|||
* {@link Window.stillSnapshotTaken|stillSnapshotTaken} is emitted; when a still image plus moving images are captured,
|
||||
* {@link Window.processingGifStarted|processingGifStarted} and {@link Window.processingGifCompleted|processingGifCompleted}
|
||||
* are emitted. The path to store the snapshots and the length of the animated GIF to capture are specified in Settings >
|
||||
* NOTE: to provide a non-default value - all previous parameters must be provided.
|
||||
* General > Snapshots.
|
||||
* @function Window.takeSnapshot
|
||||
* @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken}
|
||||
|
@ -328,7 +327,7 @@ public slots:
|
|||
* @param {number} [aspectRatio=0] - The width/height ratio of the snapshot required. If the value is <code>0</code> the
|
||||
* full resolution is used (window dimensions in desktop mode; HMD display dimensions in HMD mode), otherwise one of the
|
||||
* dimensions is adjusted in order to match the aspect ratio.
|
||||
* @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS'.
|
||||
* @param {string} [filename=""] - If this parameter is not given, the image will be saved as "hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS".
|
||||
* If this parameter is <code>""</code> then the image will be saved as ".jpg".
|
||||
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
|
||||
*
|
||||
|
@ -360,29 +359,29 @@ public slots:
|
|||
|
||||
/**jsdoc
|
||||
* Takes a still snapshot of the current view from the secondary camera that can be set up through the {@link Render} API.
|
||||
* NOTE: to provide a non-default value - all previous parameters must be provided.
|
||||
* @function Window.takeSecondaryCameraSnapshot
|
||||
* @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS'.
|
||||
* @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken}
|
||||
* signal.
|
||||
* @param {string} [filename=""] - If this parameter is not given, the image will be saved as "hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS".
|
||||
* If this parameter is <code>""</code> then the image will be saved as ".jpg".
|
||||
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
|
||||
*
|
||||
* var filename = QString();
|
||||
*/
|
||||
void takeSecondaryCameraSnapshot(const QString& filename = QString());
|
||||
void takeSecondaryCameraSnapshot(const bool& notify = true, const QString& filename = QString());
|
||||
|
||||
/**jsdoc
|
||||
* Takes a 360 snapshot given a position of the secondary camera (which does not need to have been previously set up).
|
||||
* @function Window.takeSecondaryCameraSnapshot
|
||||
* @param {vec3} [cameraPosition] - The (x, y, z) position of the camera for the 360 snapshot
|
||||
* @param {boolean} [cubemapOutputFormat=false] - If <code>true</code> then the snapshot is saved as a cube map image,
|
||||
* otherwise is saved as an equirectangular image.
|
||||
* @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS'.
|
||||
* If this parameter is <code>""</code> then the image will be saved as ".jpg".
|
||||
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
|
||||
*
|
||||
* var filename = QString();
|
||||
*/
|
||||
void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat = false, const QString& filename = QString());
|
||||
* Takes a 360° snapshot at a given position for the secondary camera. The secondary camera does not need to have been
|
||||
* set up.
|
||||
* @function Window.takeSecondaryCamera360Snapshot
|
||||
* @param {Vec3} cameraPosition - The position of the camera for the snapshot.
|
||||
* @param {boolean} [cubemapOutputFormat=false] - If <code>true</code> then the snapshot is saved as a cube map image,
|
||||
* otherwise is saved as an equirectangular image.
|
||||
* @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken}
|
||||
* signal.
|
||||
* @param {string} [filename=""] - If this parameter is not supplied, the image will be saved as "hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS".
|
||||
* If this parameter is <code>""</code> then the image will be saved as ".jpg".
|
||||
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
|
||||
*/
|
||||
void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat = false, const bool& notify = true, const QString& filename = QString());
|
||||
|
||||
/**jsdoc
|
||||
* Emit a {@link Window.connectionAdded|connectionAdded} or a {@link Window.connectionError|connectionError} signal that
|
||||
|
@ -470,7 +469,7 @@ public slots:
|
|||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @typedef Window.DisplayTexture
|
||||
* @typedef {string} Window.DisplayTexture
|
||||
*/
|
||||
bool setDisplayTexture(const QString& name);
|
||||
|
||||
|
@ -523,16 +522,21 @@ public slots:
|
|||
int openMessageBox(QString title, QString text, int buttons, int defaultButton);
|
||||
|
||||
/**jsdoc
|
||||
* Open the given resource in the Interface window or in a web browser depending on the url scheme
|
||||
* Open a URL in the Interface window or other application, depending on the URL's scheme. If the URL starts with
|
||||
* <code>hifi://</code> then that URL is navigated to in Interface, otherwise the URL is opened in the application the OS
|
||||
* associates with the URL's scheme (e.g., a Web browser for <code>http://</code>).
|
||||
* @function Window.openUrl
|
||||
* @param {string} url - The resource to open
|
||||
* @param {string} url - The URL to open.
|
||||
*/
|
||||
void openUrl(const QUrl& url);
|
||||
|
||||
/**jsdoc
|
||||
* (Android only) Open the requested Activity and optionally back to the scene when the activity is done
|
||||
* Open an Android activity and optionally return back to the scene when the activity is completed. <em>Android only.</em>
|
||||
* @function Window.openAndroidActivity
|
||||
* @param {string} activityName - The name of the activity to open. One of "Home", "Login" or "Privacy Policy"
|
||||
* @param {string} activityName - The name of the activity to open: one of <code>"Home"</code>, <code>"Login"</code>, or
|
||||
* <code>"Privacy Policy"</code>.
|
||||
* @param {boolean} backToScene - If <code>true</code>, the user is automatically returned back to the scene when the
|
||||
* activity is completed.
|
||||
*/
|
||||
void openAndroidActivity(const QString& activityName, const bool backToScene);
|
||||
|
||||
|
@ -607,7 +611,8 @@ signals:
|
|||
void stillSnapshotTaken(const QString& pathStillSnapshot, bool notify);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when a still equirectangular snapshot has been taken by calling {@link Window.takeSecondaryCamera360Snapshot|takeSecondaryCamera360Snapshot}
|
||||
* Triggered when a still 360° snapshot has been taken by calling
|
||||
* {@link Window.takeSecondaryCamera360Snapshot|takeSecondaryCamera360Snapshot}.
|
||||
* @function Window.snapshot360Taken
|
||||
* @param {string} pathStillSnapshot - The path and name of the snapshot image file.
|
||||
* @param {boolean} notify - The value of the <code>notify</code> parameter that {@link Window.takeSecondaryCamera360Snapshot|takeSecondaryCamera360Snapshot}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue