Merge branch 'master' into snoretoast

Two simple conflicts fixed.
This commit is contained in:
Simon Walton 2018-06-13 11:19:56 -07:00
commit 01ef636411
379 changed files with 8364 additions and 4334 deletions

View file

@ -9,15 +9,17 @@ Note: The prerequisites will require about 10 GB of space on your drive. You wil
If you dont 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.

View file

@ -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')
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -37,7 +37,7 @@ task clean(type: Delete) {
ext {
RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0'
RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV'
BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : ''
STABLE_BUILD = project.hasProperty('STABLE_BUILD') ? project.getProperty('STABLE_BUILD') : '0'
EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : ''
QT5_DEPS = [
'Qt5Concurrent',
@ -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

View file

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

View file

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

View file

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

View file

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

View file

@ -183,7 +183,7 @@ protected:
bool _isShuttingDown = false;
HTTPManager* _httpManager;
std::unique_ptr<HTTPManager> _httpManager;
int _statusPort;
QString _statusHost;

View file

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

View file

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

View file

@ -17,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
View 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

View file

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

View file

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

View file

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

View file

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

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

View 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)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,6 +13,7 @@
#include <openssl/x509.h>
#include <QtCore/QDataStream>
#include <QtCore/QJsonDocument>
#include <QtCore/QTimer>
#include <QtNetwork/QNetworkReply>

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,002 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

View file

@ -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 != ""
}
}

View file

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

View file

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

View file

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

View file

@ -16,10 +16,11 @@ import QtQuick 2.5
import QtGraphicalEffects 1.0
import "toolbars"
import "../styles-uit"
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
Column {
id: root;
visible: false;
visible: !!suggestions.count;
property int cardWidth: 212;
property int cardHeight: 152;
@ -32,21 +33,37 @@ Column {
property int stackedCardShadowHeight: 4;
property int labelSize: 20;
property string metaverseServerUrl: '';
property string protocol: '';
property string actions: 'snapshot';
// sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick.
property string labelText: actions;
property string filter: '';
onFilterChanged: filterChoicesByText();
property var goFunction: null;
property var rpc: null;
property var http: null;
HifiConstants { id: hifi }
ListModel { id: suggestions; }
Component.onCompleted: suggestions.getFirstPage();
HifiModels.PSFListModel {
id: suggestions;
http: root.http;
property var options: [
'include_actions=' + actions,
'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
'require_online=true',
'protocol=' + encodeURIComponent(Window.protocolSignature())
];
endpoint: '/api/v1/user_stories?' + options.join('&');
itemsPerPage: 3;
processPage: function (data) {
return data.user_stories.map(makeModelData);
};
listModelName: actions;
listView: scroll;
searchFilter: filter;
}
function resolveUrl(url) {
return (url.indexOf('/') === 0) ? (metaverseServerUrl + url) : url;
return (url.indexOf('/') === 0) ? (Account.metaverseServerURL + url) : url;
}
function makeModelData(data) { // create a new obj from data
// ListModel elements will only ever have those properties that are defined by the first obj that is added.
@ -55,16 +72,11 @@ Column {
tags = data.tags || [data.action, data.username],
description = data.description || "",
thumbnail_url = data.thumbnail_url || "";
if (actions === 'concurrency,snapshot') {
// A temporary hack for simulating announcements. We won't use this in production, but if requested, we'll use this data like announcements.
data.details.connections = 4;
data.action = 'announcement';
}
return {
place_name: name,
username: data.username || "",
path: data.path || "",
created_at: data.created_at || "",
created_at: data.created_at || data.updated_at || "", // FIXME why aren't we getting created_at?
action: data.action || "",
thumbnail_url: resolveUrl(thumbnail_url),
image_url: resolveUrl(data.details && data.details.image_url),
@ -74,125 +86,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;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -52,6 +52,7 @@
#include "MyHead.h"
#include "MySkeletonModel.h"
#include "AnimUtil.h"
#include "Application.h"
#include "AvatarManager.h"
#include "AvatarActionHold.h"
@ -422,12 +423,12 @@ void MyAvatar::update(float deltaTime) {
}
#ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE
glm::vec3 p = transformPoint(getSensorToWorldMatrix(), getControllerPoseInAvatarFrame(controller::Pose::HEAD) *
glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y));
DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f));
p = transformPoint(getSensorToWorldMatrix(), getHMDSensorPosition() +
glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y));
DebugDraw::getInstance().addMarker("facing", getOrientation(), p, glm::vec4(1.0f));
auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation());
glm::vec3 worldFacingAverage = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y));
glm::vec3 worldFacing = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y));
DebugDraw::getInstance().drawRay(worldHeadPos, worldHeadPos + worldFacing, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
DebugDraw::getInstance().drawRay(worldHeadPos, worldHeadPos + worldFacingAverage, glm::vec4(0.0f, 0.0f, 1.0f, 1.0f));
#endif
if (_goToPending) {
@ -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());
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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-&lt;user name&gt-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-&lt;user name&gt;-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&deg; 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-&lt;user name&gt;-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&deg; 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