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

Merge 'master' into 'workload'

This commit is contained in:
Andrew Meadows 2018-06-18 10:30:13 -07:00
commit 6d422e4678
292 changed files with 16552 additions and 6052 deletions
INSTALL.md
android
app
build.gradle
src/main/java/io/highfidelity/hifiinterface
build.gradle
assignment-client/src
cmake
domain-server/src
ice-server/src
interface

View file

@ -21,6 +21,7 @@ To produce an executable installer on Windows, the following are required:
- [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
- [nsisSlideshow Plug-in for Nullsoft](http://nsis.sourceforge.net/NsisSlideshow_plug-in) - 1.7
- [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in)
- [ApplicationID plug-in for Nullsoft](http://nsis.sourceforge.net/ApplicationID_plug-in) - 1.0
Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System.

View file

@ -4,12 +4,15 @@ android {
compileSdkVersion 26
//buildToolsVersion '27.0.3'
def appVersionCode = Integer.valueOf(RELEASE_NUMBER ?: 1)
def appVersionName = RELEASE_NUMBER ?: "1.0"
defaultConfig {
applicationId "io.highfidelity.hifiinterface"
minSdkVersion 24
targetSdkVersion 26
versionCode 1
versionName "1.0"
versionCode appVersionCode
versionName appVersionName
ndk { abiFilters 'arm64-v8a' }
externalNativeBuild {
cmake {
@ -22,7 +25,7 @@ android {
'-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED,
'-DRELEASE_NUMBER=' + RELEASE_NUMBER,
'-DRELEASE_TYPE=' + RELEASE_TYPE,
'-DBUILD_BRANCH=' + BUILD_BRANCH,
'-DSTABLE_BUILD=' + STABLE_BUILD,
'-DDISABLE_QML=OFF',
'-DDISABLE_KTX_CACHE=OFF'
}
@ -128,4 +131,3 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
}

View file

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

View file

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

View file

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

View file

@ -37,7 +37,7 @@ task clean(type: Delete) {
ext {
RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0'
RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV'
BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : ''
STABLE_BUILD = project.hasProperty('STABLE_BUILD') ? project.getProperty('STABLE_BUILD') : '0'
EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : ''
QT5_DEPS = [
'Qt5Concurrent',
@ -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 = ''
def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/'
def qtFile='https://hifi-public.s3.amazonaws.com/austin/android/qt-5.9.3_linux_armv8-libcpp_openssl.tgz'
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 = 'https://hifi-public.s3.amazonaws.com/austin/android/qt-5.9.3_osx_armv8-libcpp_openssl.tgz'
qtFile = 'qt-5.9.3_osx_armv8-libcpp_openssl.tgz'
qtChecksum='4b02de9d67d6bfb202355a808d2d9c59'
qtVersionId='HygCmtMLPYioyil0DfXckGVzhw2SXZA9'
qtVersionId='2gfgoYCggJGyXxKiazaPGsMs1Gn9j4og'
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
qtFile = 'https://hifi-public.s3.amazonaws.com/austin/android/qt-5.9.3_win_armv8-libcpp_openssl.tgz'
qtFile = 'qt-5.9.3_win_armv8-libcpp_openssl.tgz'
qtChecksum='c3e25db64002d0f43cf565e0ef708911'
qtVersionId='HeVObSVLCBoc7yY7He1oBMvPIH0VkClT'
qtVersionId='xKIteC6HO0xrmcWeMmhQcmKyPEsnUrcZ'
}
def packages = [
@ -88,79 +88,84 @@ def packages = [
checksum: qtChecksum,
],
bullet: [
file: 'https://hifi-public.s3.amazonaws.com/dependencies/android/bullet-2.88_armv8-libcpp.tgz',
file: 'bullet-2.88_armv8-libcpp.tgz',
versionId: 'S8YaoED0Cl8sSb8fSV7Q2G1lQJSNDxqg',
checksum: '81642779ccb110f8c7338e8739ac38a0',
],
draco: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/draco_armv8-libcpp.tgz',
versionId: 'cA3tVJSmkvb1naA3l6D_Jv2Noh.4yc4m',
file: 'draco_armv8-libcpp.tgz',
versionId: '3.B.uBj31kWlgND3_R2xwQzT_TP6Dz_8',
checksum: '617a80d213a5ec69fbfa21a1f2f738cd',
],
glad: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/glad_armv8-libcpp.zip',
versionId: 'Q9szthzeye8fFyAA.cY26Lgn2B8kezEE',
file: 'glad_armv8-libcpp.zip',
versionId: 'r5Zran.JSCtvrrB6Q4KaqfIoALPw3lYY',
checksum: 'a8ee8584cf1ccd34766c7ddd9d5e5449',
],
glm: [
file: 'https://hifi-public.s3.amazonaws.com/dependencies/android/glm-0.9.8.5-patched.tgz',
file: 'glm-0.9.8.5-patched.tgz',
versionId: 'cskfMoJrFlAeqI3WPxemyO_Cxt7rT9EJ',
checksum: '067b5fe16b220b5b1a1039ba51b062ae',
],
gvr: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/gvrsdk_v1.101.0.tgz',
versionId: 'UTberAIFraEfF9IVjoV66u1DTPTopgeY',
file: 'gvrsdk_v1.101.0.tgz',
versionId: 'nqBV_j81Uc31rC7bKIrlya_Hah4v3y5r',
checksum: '57fd02baa069176ba18597a29b6b4fc7',
],
nvtt: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/nvtt_armv8-libcpp.zip',
versionId: 'vLqrqThvpq4gp75BHMAqO6HhfTXaa0An',
file: 'nvtt_armv8-libcpp.zip',
versionId: 'lmkBVR5t4UF1UUwMwEirnk9H_8Nt90IO',
checksum: 'eb46d0b683e66987190ed124aabf8910',
sharedLibFolder: 'lib',
includeLibs: ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so'],
],
openssl: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/openssl-1.1.0g_armv8.tgz',
versionId: 'DmahmSGFS4ltpHyTdyQvv35WOeUOiib9',
file: 'openssl-1.1.0g_armv8.tgz',
versionId: 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW',
checksum: 'cabb681fbccd79594f65fcc266e02f32',
],
polyvox: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/polyvox_armv8-libcpp.tgz',
versionId: 'LDJtzMTvdm4SAc2KYg8Cg6uwWk4Vq3e3',
checksum: '349ad5b72aaf2749ca95d847e60c5314',
file: 'polyvox_armv8-libcpp.tgz',
versionId: 'A2kbKiNhpIenGq23bKRRzg7IMAI5BI92',
checksum: 'dba88b3a098747af4bb169e9eb9af57e',
sharedLibFolder: 'lib',
includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'],
],
tbb: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/tbb-2018_U1_armv8_libcpp.tgz',
versionId: 'YZliDD8.Menh1IVXKEuLPeO3xAjJ1UdF',
file: 'tbb-2018_U1_armv8_libcpp.tgz',
versionId: 'mrRbWnv4O4evcM1quRH43RJqimlRtaKB',
checksum: '20768f298f53b195e71b414b0ae240c4',
sharedLibFolder: 'lib/release',
includeLibs: ['libtbb.so', 'libtbbmalloc.so'],
],
hifiAC: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/libplugins_libhifiCodec.zip',
versionId: 'mzKhsRCgVmloqq5bvE.0IwYK1NjGQc_G',
file: 'libplugins_libhifiCodec.zip',
versionId: 'i31pW.qNbvFOXRxbyiJUxg3sphaFNmZU',
checksum: '9412a8e12c88a4096c1fc843bb9fe52d',
sharedLibFolder: '',
includeLibs: ['libplugins_libhifiCodec.so']
],
etc2comp: [
file: 'etc2comp-patched-armv8-libcpp.tgz',
versionId: 'bHhGECRAQR1vkpshBcK6ByNc1BQIM8gU',
checksum: '14b02795d774457a33bbc60e00a786bc'
]
]
def scribeLocalFile='scribe' + EXEC_SUFFIX
def scribeFile='https://hifi-public.s3.amazonaws.com/austin/android/scribe_linux_x86_64'
def scribeFile='scribe_linux_x86_64'
def scribeChecksum='ca4b904f52f4f993c29175ba96798fa6'
def scribeVersion='wgpf4dB2Ltzg4Lb2jJ4nPFsHoDkmK_OO'
def scribeVersion='u_iTrJDaE95i2abTPXOpPZckGBIim53G'
if (Os.isFamily(Os.FAMILY_MAC)) {
scribeFile = 'https://hifi-public.s3.amazonaws.com/austin/android/scribe_osx_x86_64'
scribeFile = 'scribe_osx_x86_64'
scribeChecksum='72db9d32d4e1e50add755570ac5eb749'
scribeVersion='o_NbPrktzEYtBkQf3Tn7zc1nZWzM52w6'
scribeVersion='DAW0DmnjCRib4MD8x93bgc2Z2MpPojZC'
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
scribeFile = 'https://hifi-public.s3.amazonaws.com/austin/android/scribe_win32_x86_64.exe'
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
@ -608,4 +615,4 @@ task testElf (dependsOn: 'externalNativeBuildDebug') {
}
}
}
*/
*/

View file

@ -291,18 +291,6 @@ AssetServer::AssetServer(ReceivedMessage& message) :
_bakingTaskPool(this),
_filesizeLimit(AssetUtils::MAX_UPLOAD_SIZE)
{
// store the current state of image compression so we can reset it when this assignment is complete
_wasColorTextureCompressionEnabled = image::isColorTexturesCompressionEnabled();
_wasGrayscaleTextureCompressionEnabled = image::isGrayscaleTexturesCompressionEnabled();
_wasNormalTextureCompressionEnabled = image::isNormalTexturesCompressionEnabled();
_wasCubeTextureCompressionEnabled = image::isCubeTexturesCompressionEnabled();
// enable compression in image library
image::setColorTexturesCompressionEnabled(true);
image::setGrayscaleTexturesCompressionEnabled(true);
image::setNormalTexturesCompressionEnabled(true);
image::setCubeTexturesCompressionEnabled(true);
BAKEABLE_TEXTURE_EXTENSIONS = image::getSupportedFormats();
qDebug() << "Supported baking texture formats:" << BAKEABLE_MODEL_EXTENSIONS;
@ -354,12 +342,6 @@ void AssetServer::aboutToFinish() {
while (_pendingBakes.size() > 0) {
QCoreApplication::processEvents();
}
// re-set defaults in image library
image::setColorTexturesCompressionEnabled(_wasCubeTextureCompressionEnabled);
image::setGrayscaleTexturesCompressionEnabled(_wasGrayscaleTextureCompressionEnabled);
image::setNormalTexturesCompressionEnabled(_wasNormalTextureCompressionEnabled);
image::setCubeTexturesCompressionEnabled(_wasCubeTextureCompressionEnabled);
}
void AssetServer::run() {

View file

@ -167,11 +167,6 @@ private:
using RequestQueue = QVector<QPair<QSharedPointer<ReceivedMessage>, SharedNodePointer>>;
RequestQueue _queuedRequests;
bool _wasColorTextureCompressionEnabled { false };
bool _wasGrayscaleTextureCompressionEnabled { false };
bool _wasNormalTextureCompressionEnabled { false };
bool _wasCubeTextureCompressionEnabled { false };
uint64_t _filesizeLimit;
};

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)) {
@ -395,21 +396,26 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
quint64 end = usecTimestampNow();
_stats.toByteArrayElapsedTime += (end - start);
static const int MAX_ALLOWED_AVATAR_DATA = (1400 - NUM_BYTES_RFC4122_UUID);
if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) {
qCWarning(avatars) << "otherAvatar.toByteArray() resulted in very large buffer:" << bytes.size() << "... attempt to drop facial data";
auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID;
if (bytes.size() > maxAvatarDataBytes) {
qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
<< "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data";
dropFaceTracking = true; // first try dropping the facial data
bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther);
if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) {
qCWarning(avatars) << "otherAvatar.toByteArray() without facial data resulted in very large buffer:" << bytes.size() << "... reduce to MinimumData";
if (bytes.size() > maxAvatarDataBytes) {
qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
<< "without facial data resulted in very large buffer of" << bytes.size()
<< "bytes - reducing to MinimumData";
bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther,
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther);
if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) {
qCWarning(avatars) << "otherAvatar.toByteArray() MinimumData resulted in very large buffer:" << bytes.size() << "... FAIL!!";
if (bytes.size() > maxAvatarDataBytes) {
qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
<< "MinimumData resulted in very large buffer of" << bytes.size()
<< "bytes - refusing to send avatar";
includeThisAvatar = false;
}
}

View file

@ -231,19 +231,19 @@ void OctreeServer::trackProcessWaitTime(float time) {
OctreeServer::OctreeServer(ReceivedMessage& message) :
ThreadedAssignment(message),
_argc(0),
_argv(NULL),
_parsedArgV(NULL),
_httpManager(NULL),
_argv(nullptr),
_parsedArgV(nullptr),
_httpManager(nullptr),
_statusPort(0),
_packetsPerClientPerInterval(10),
_packetsTotalPerInterval(DEFAULT_PACKETS_PER_INTERVAL),
_tree(NULL),
_tree(nullptr),
_wantPersist(true),
_debugSending(false),
_debugReceiving(false),
_verboseDebug(false),
_octreeInboundPacketProcessor(NULL),
_persistThread(NULL),
_octreeInboundPacketProcessor(nullptr),
_persistManager(nullptr),
_started(time(0)),
_startedUSecs(usecTimestampNow())
{
@ -266,11 +266,8 @@ OctreeServer::~OctreeServer() {
_octreeInboundPacketProcessor->deleteLater();
}
if (_persistThread) {
_persistThread->terminating();
_persistThread->terminate();
_persistThread->deleteLater();
}
qDebug() << "Waiting for persist thread to come down";
_persistThread.wait();
// cleanup our tree here...
qDebug() << qPrintable(_safeServerName) << "server START cleaning up octree... [" << this << "]";
@ -285,7 +282,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) {
@ -1053,19 +1050,13 @@ void OctreeServer::readConfiguration() {
_persistAsFileType = "json.gz";
_persistInterval = OctreePersistThread::DEFAULT_PERSIST_INTERVAL;
readOptionInt(QString("persistInterval"), settingsSectionObject, _persistInterval);
qDebug() << "persistInterval=" << _persistInterval;
bool noBackup;
readOptionBool(QString("NoBackup"), settingsSectionObject, noBackup);
_wantBackup = !noBackup;
qDebug() << "wantBackup=" << _wantBackup;
if (!readOptionString("backupDirectoryPath", settingsSectionObject, _backupDirectoryPath)) {
_backupDirectoryPath = "";
int result { -1 };
readOptionInt(QString("persistInterval"), settingsSectionObject, result);
if (result != -1) {
_persistInterval = std::chrono::milliseconds(result);
}
qDebug() << "backupDirectoryPath=" << _backupDirectoryPath;
qDebug() << "persistInterval=" << _persistInterval.count();
readOptionBool(QString("persistFileDownload"), settingsSectionObject, _persistFileDownload);
qDebug() << "persistFileDownload=" << _persistFileDownload;
@ -1129,111 +1120,14 @@ void OctreeServer::run() {
}
void OctreeServer::domainSettingsRequestComplete() {
if (_state != OctreeServerState::WaitingForDomainSettings) {
qCWarning(octree_server) << "Received domain settings after they have already been received";
return;
}
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket");
packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket");
packetReceiver.registerListener(PacketType::OctreeDataFileReply, this, "handleOctreeDataFileReply");
packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket");
qDebug(octree_server) << "Received domain settings";
readConfiguration();
_state = OctreeServerState::WaitingForOctreeDataNegotation;
auto nodeList = DependencyManager::get<NodeList>();
const DomainHandler& domainHandler = nodeList->getDomainHandler();
auto packet = NLPacket::create(PacketType::OctreeDataFileRequest, -1, true, false);
OctreeUtils::RawOctreeData data;
qCDebug(octree_server) << "Reading octree data from" << _persistAbsoluteFilePath;
if (data.readOctreeDataInfoFromFile(_persistAbsoluteFilePath)) {
qCDebug(octree_server) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.version << ")";
packet->writePrimitive(true);
auto id = data.id.toRfc4122();
packet->write(id);
packet->writePrimitive(data.version);
} else {
qCWarning(octree_server) << "No octree data found";
packet->writePrimitive(false);
}
qCDebug(octree_server) << "Sending request for octree data to DS";
nodeList->sendPacket(std::move(packet), domainHandler.getSockAddr());
}
void OctreeServer::handleOctreeDataFileReply(QSharedPointer<ReceivedMessage> message) {
if (_state != OctreeServerState::WaitingForOctreeDataNegotation) {
qCWarning(octree_server) << "Server received ocree data file reply but is not currently negotiating.";
return;
}
bool includesNewData;
message->readPrimitive(&includesNewData);
QByteArray replaceData;
if (includesNewData) {
replaceData = message->readAll();
qDebug() << "Got reply to octree data file request, new data sent";
} else {
qDebug() << "Got reply to octree data file request, current entity data is sufficient";
OctreeUtils::RawEntityData data;
qCDebug(octree_server) << "Reading octree data from" << _persistAbsoluteFilePath;
if (data.readOctreeDataInfoFromFile(_persistAbsoluteFilePath)) {
if (data.id.isNull()) {
qCDebug(octree_server) << "Current octree data has a null id, updating";
data.resetIdAndVersion();
QFile file(_persistAbsoluteFilePath);
if (file.open(QIODevice::WriteOnly)) {
auto entityData = data.toGzippedByteArray();
file.write(entityData);
file.close();
} else {
qCDebug(octree_server) << "Failed to update octree data";
}
}
}
}
_state = OctreeServerState::Running;
beginRunning(replaceData);
}
void OctreeServer::beginRunning(QByteArray replaceData) {
if (_state != OctreeServerState::Running) {
qCWarning(octree_server) << "Server is not running";
return;
}
auto nodeList = DependencyManager::get<NodeList>();
// we need to ask the DS about agents so we can ping/reply with them
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
beforeRun(); // after payload has been processed
connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer)));
connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
#ifndef WIN32
setvbuf(stdout, NULL, _IOLBF, 0);
#endif
nodeList->linkedDataCreateCallback = [this](Node* node) {
auto queryNodeData = createOctreeQueryNode();
queryNodeData->init();
node->setLinkedData(std::move(queryNodeData));
};
srand((unsigned)time(0));
// if we want Persistence, set up the local file and persist thread
if (_wantPersist) {
static const QString ENTITY_PERSIST_EXTENSION = ".json.gz";
@ -1289,40 +1183,40 @@ void OctreeServer::beginRunning(QByteArray replaceData) {
}
auto persistFileDirectory = QFileInfo(_persistAbsoluteFilePath).absolutePath();
if (_backupDirectoryPath.isEmpty()) {
// Use the persist file's directory to store backups
_backupDirectoryPath = persistFileDirectory;
} else {
// The backup directory has been set.
// If relative, make it relative to the entities directory in the application data directory
// If absolute, no resolution is necessary
QDir backupDirectory { _backupDirectoryPath };
QString absoluteBackupDirectory;
if (backupDirectory.isRelative()) {
absoluteBackupDirectory = QDir(PathUtils::getAppDataFilePath("entities/")).absoluteFilePath(_backupDirectoryPath);
absoluteBackupDirectory = QDir(absoluteBackupDirectory).absolutePath();
} else {
absoluteBackupDirectory = backupDirectory.absolutePath();
}
backupDirectory = QDir(absoluteBackupDirectory);
if (!backupDirectory.exists()) {
if (backupDirectory.mkpath(".")) {
qDebug() << "Created backup directory";
} else {
qDebug() << "ERROR creating backup directory, using persist file directory";
_backupDirectoryPath = persistFileDirectory;
}
} else {
_backupDirectoryPath = absoluteBackupDirectory;
}
}
qDebug() << "Backups will be stored in: " << _backupDirectoryPath;
// now set up PersistThread
_persistThread = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval,
_wantBackup, _settings, _debugTimestampNow, _persistAsFileType, replaceData);
_persistThread->initialize(true);
_persistManager = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _persistInterval, _debugTimestampNow,
_persistAsFileType);
_persistManager->moveToThread(&_persistThread);
connect(&_persistThread, &QThread::finished, _persistManager, &QObject::deleteLater);
connect(&_persistThread, &QThread::started, _persistManager, &OctreePersistThread::start);
connect(_persistManager, &OctreePersistThread::loadCompleted, this, [this]() {
beginRunning();
});
_persistThread.start();
} else {
beginRunning();
}
}
void OctreeServer::beginRunning() {
auto nodeList = DependencyManager::get<NodeList>();
// we need to ask the DS about agents so we can ping/reply with them
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
beforeRun(); // after payload has been processed
connect(nodeList.data(), &NodeList::nodeAdded, this, &OctreeServer::nodeAdded);
connect(nodeList.data(), &NodeList::nodeKilled, this, &OctreeServer::nodeKilled);
nodeList->linkedDataCreateCallback = [this](Node* node) {
auto queryNodeData = createOctreeQueryNode();
queryNodeData->init();
node->setLinkedData(std::move(queryNodeData));
};
srand((unsigned)time(0));
// set up our OctreeServerPacketProcessor
_octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this);
@ -1385,7 +1279,7 @@ void OctreeServer::aboutToFinish() {
qDebug() << qPrintable(_safeServerName) << "inform Octree Inbound Packet Processor that we are shutting down...";
// we're going down - set the NodeList linkedDataCallback to NULL so we do not create any more OctreeQueryNode objects.
// we're going down - set the NodeList linkedDataCallback to nullptr so we do not create any more OctreeQueryNode objects.
// This ensures that we don't get any more newly connecting nodes
DependencyManager::get<NodeList>()->linkedDataCreateCallback = nullptr;
@ -1403,9 +1297,8 @@ void OctreeServer::aboutToFinish() {
// which waits on the thread to be done before returning
_sendThreads.clear(); // Cleans up all the send threads.
if (_persistThread) {
_persistThread->aboutToFinish();
_persistThread->terminating();
if (_persistManager) {
_persistThread.quit();
}
qDebug() << qPrintable(_safeServerName) << "server ENDING about to finish...";

View file

@ -33,12 +33,6 @@ Q_DECLARE_LOGGING_CATEGORY(octree_server)
const int DEFAULT_PACKETS_PER_INTERVAL = 2000; // some 120,000 packets per second total
enum class OctreeServerState {
WaitingForDomainSettings,
WaitingForOctreeDataNegotation,
Running
};
/// Handles assignments of type OctreeServer - sending octrees to various clients.
class OctreeServer : public ThreadedAssignment, public HTTPRequestHandler {
Q_OBJECT
@ -46,8 +40,6 @@ public:
OctreeServer(ReceivedMessage& message);
~OctreeServer();
OctreeServerState _state { OctreeServerState::WaitingForDomainSettings };
/// allows setting of run arguments
void setArguments(int argc, char** argv);
@ -68,12 +60,12 @@ public:
static void clientConnected() { _clientCount++; }
static void clientDisconnected() { _clientCount--; }
bool isInitialLoadComplete() const { return (_persistThread) ? _persistThread->isInitialLoadComplete() : true; }
bool isPersistEnabled() const { return (_persistThread) ? true : false; }
quint64 getLoadElapsedTime() const { return (_persistThread) ? _persistThread->getLoadElapsedTime() : 0; }
QString getPersistFilename() const { return (_persistThread) ? _persistThread->getPersistFilename() : ""; }
QString getPersistFileMimeType() const { return (_persistThread) ? _persistThread->getPersistFileMimeType() : "text/plain"; }
QByteArray getPersistFileContents() const { return (_persistThread) ? _persistThread->getPersistFileContents() : QByteArray(); }
bool isInitialLoadComplete() const { return (_persistManager) ? _persistManager->isInitialLoadComplete() : true; }
bool isPersistEnabled() const { return (_persistManager) ? true : false; }
quint64 getLoadElapsedTime() const { return (_persistManager) ? _persistManager->getLoadElapsedTime() : 0; }
QString getPersistFilename() const { return (_persistManager) ? _persistManager->getPersistFilename() : ""; }
QString getPersistFileMimeType() const { return (_persistManager) ? _persistManager->getPersistFileMimeType() : "text/plain"; }
QByteArray getPersistFileContents() const { return (_persistManager) ? _persistManager->getPersistFileContents() : QByteArray(); }
// Subclasses must implement these methods
virtual std::unique_ptr<OctreeQueryNode> createOctreeQueryNode() = 0;
@ -149,7 +141,6 @@ private slots:
void domainSettingsRequestComplete();
void handleOctreeQueryPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleOctreeDataNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleOctreeDataFileReply(QSharedPointer<ReceivedMessage> message);
void removeSendThread();
protected:
@ -171,7 +162,7 @@ protected:
QString getConfiguration();
QString getStatusLink();
void beginRunning(QByteArray replaceData);
void beginRunning();
UniqueSendThread createSendThread(const SharedNodePointer& node);
virtual UniqueSendThread newSendThread(const SharedNodePointer& node) = 0;
@ -183,14 +174,13 @@ protected:
bool _isShuttingDown = false;
HTTPManager* _httpManager;
std::unique_ptr<HTTPManager> _httpManager;
int _statusPort;
QString _statusHost;
QString _persistFilePath;
QString _persistAbsoluteFilePath;
QString _persistAsFileType;
QString _backupDirectoryPath;
int _packetsPerClientPerInterval;
int _packetsTotalPerInterval;
OctreePointer _tree; // this IS a reaveraging tree
@ -200,13 +190,11 @@ protected:
bool _debugTimestampNow;
bool _verboseDebug;
OctreeInboundPacketProcessor* _octreeInboundPacketProcessor;
OctreePersistThread* _persistThread;
OctreePersistThread* _persistManager;
QThread _persistThread;
int _persistInterval;
bool _wantBackup;
std::chrono::milliseconds _persistInterval;
bool _persistFileDownload;
QString _backupExtensionFormat;
int _backupInterval;
int _maxBackupVersions;
time_t _started;

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

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

@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC68.zip
URL_MD5 a068f74d4045e257cfa7926fe6e38ad5
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC68-v2.zip
URL_MD5 f7d290471baf7f5694c147217b8fc548
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

View file

@ -17,14 +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
@ -46,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")
@ -75,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 --abbrev=7 --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)
@ -126,9 +173,10 @@ macro(SET_PACKAGING_PARAMETERS)
if (PRODUCTION_BUILD)
set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface")
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

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

@ -49,3 +49,4 @@ set(ADD_REMOVE_ICON_PATH "@ADD_REMOVE_ICON_PATH@")
set(SERVER_COMPONENT_CONDITIONAL "@SERVER_COMPONENT_CONDITIONAL@")
set(CLIENT_COMPONENT_CONDITIONAL "@CLIENT_COMPONENT_CONDITIONAL@")
set(INSTALLER_TYPE "@INSTALLER_TYPE@")
set(APP_USER_MODEL_ID "@APP_USER_MODEL_ID@")

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

@ -905,6 +905,8 @@ Function HandlePostInstallOptions
${If} $DesktopServerState == ${BST_CHECKED}
CreateShortCut "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
!insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES
; Set appUserModelId
ApplicationID::Set "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "@APP_USER_MODEL_ID@"
${Else}
!insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO
${EndIf}
@ -1162,6 +1164,8 @@ Section "-Core installation"
${If} @SERVER_COMPONENT_CONDITIONAL@
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" \
"$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
; Set appUserModelId
ApplicationID::Set "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" "@APP_USER_MODEL_ID@"
${EndIf}
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\@UNINSTALLER_NAME@"

View file

@ -1,4 +1,8 @@
{
"releaseType": "@RELEASE_TYPE@",
"buildIdentifier": "@BUILD_VERSION@"
"buildNumber": "@BUILD_NUMBER@",
"stableBuild": "@STABLE_BUILD@",
"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>

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
@ -1121,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
@ -2683,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;
}
@ -2945,6 +2942,12 @@ bool DomainServer::shouldReplicateNode(const Node& node) {
}
};
bool DomainServer::isAssetServerEnabled() {
static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled";
return _settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool();
}
void DomainServer::nodeAdded(SharedNodePointer node) {
// we don't use updateNodeWithData, so add the DomainServerNodeData to the node here
node->setLinkedData(std::unique_ptr<DomainServerNodeData> { new DomainServerNodeData() });

View file

@ -72,6 +72,8 @@ public:
static const QString REPLACEMENT_FILE_EXTENSION;
bool isAssetServerEnabled();
public slots:
/// Called by NodeList to inform us a node has been added
void nodeAdded(SharedNodePointer node);
@ -220,7 +222,7 @@ private:
DomainGatekeeper _gatekeeper;
HTTPManager _httpManager;
HTTPSManager* _httpsManager;
std::unique_ptr<HTTPSManager> _httpsManager;
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;

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()
@ -315,18 +309,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
@ -355,13 +358,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

Binary file not shown.

After

(image error) Size: 1.5 KiB

Binary file not shown.

After

(image error) Size: 1.5 KiB

View file

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

Binary file not shown.

After

(image error) Size: 22 KiB

Binary file not shown.

After

(image error) Size: 1.1 MiB

Binary file not shown.

After

(image error) Size: 1,002 KiB

Binary file not shown.

After

(image error) Size: 525 KiB

View file

@ -171,6 +171,10 @@ FocusScope {
}
}
function textAt(index) {
return comboBox.textAt(index);
}
HifiControls.Label {
id: comboBoxLabel
text: root.label

View file

@ -17,6 +17,10 @@ import "../controls-uit" as HifiControls
SpinBox {
id: spinBox
HifiConstants {
id: hifi
}
property int colorScheme: hifi.colorSchemes.light
readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light
property string label: ""
@ -31,8 +35,8 @@ SpinBox {
property real maximumValue: 0.0
property real realValue: 0.0
property real realFrom: 0.0
property real realTo: 100.0
property real realFrom: minimumValue
property real realTo: maximumValue
property real realStepSize: 1.0
signal editingFinished()
@ -53,7 +57,8 @@ SpinBox {
onValueChanged: realValue = value/factor
stepSize: realStepSize*factor
value: realValue*factor
value: Math.round(realValue*factor)
to : realTo*factor
from : realFrom*factor
@ -81,6 +86,7 @@ SpinBox {
}
valueFromText: function(text, locale) {
spinBox.value = 0; // Force valueChanged signal to be emitted so that validator fires.
return Number.fromLocaleString(locale, text)*factor;
}
@ -110,7 +116,7 @@ SpinBox {
anchors.centerIn: parent
text: hifi.glyphs.caratUp
size: hifi.dimensions.spinnerSize
color: spinBox.down.pressed || spinBox.up.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray
color: spinBox.up.pressed || spinBox.up.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray
}
}
@ -149,26 +155,14 @@ SpinBox {
visible: spinBox.labelInside != ""
}
// MouseArea {
// anchors.fill: parent
// propagateComposedEvents: true
// onWheel: {
// if(spinBox.activeFocus)
// wheel.accepted = false
// else
// wheel.accepted = true
// }
// onPressed: {
// mouse.accepted = false
// }
// onReleased: {
// mouse.accepted = false
// }
// onClicked: {
// mouse.accepted = false
// }
// onDoubleClicked: {
// mouse.accepted = false
// }
// }
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: {
if (wheel.angleDelta.y > 0)
value += stepSize
else
value -= stepSize
}
}
}

View file

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

View file

@ -331,6 +331,7 @@ ModalWindow {
}
onFolderChanged: {
d.clearSelection();
fileTableModel.update(); // Update once the data from the folder change is available.
}
@ -450,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; }
@ -472,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 {
@ -481,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"),
@ -492,9 +493,6 @@ ModalWindow {
rows++;
}
filesModel = newFilesModel;
d.clearSelection();
}
}

View file

@ -332,6 +332,7 @@ ModalWindow {
}
onFolderChanged: {
d.clearSelection();
fileTableModel.update(); // Update once the data from the folder change is available.
}
@ -451,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; }
@ -473,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 {
@ -482,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"),
@ -493,9 +494,6 @@ ModalWindow {
rows++;
}
filesModel = newFilesModel;
d.clearSelection();
}
}

View file

@ -295,7 +295,8 @@ TabletModalWindow {
}
onFolderChanged: {
fileTableModel.update()
d.clearSelection();
fileTableModel.update();
}
function getItem(index, field) {
@ -413,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; }
@ -435,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 {
@ -444,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"),
@ -455,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

@ -119,8 +119,8 @@ Item {
colorScheme: hifi.colorSchemes.dark
currentIndex: attachment ? model.indexOf(attachment.jointName) : -1
onCurrentIndexChanged: {
if (completed && attachment && currentIndex != -1 && currentText && currentText !== attachment.jointName) {
attachment.jointName = currentText;
if (completed && attachment && currentIndex != -1 && attachment.jointName !== model[currentIndex]) {
attachment.jointName = model[currentIndex];
updateAttachment();
}
}

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

@ -69,10 +69,15 @@ Item {
id: stack
initialItem: inputConfiguration
property alias messageVisible: imageMessageBox.visible
property alias selectedPlugin: box.currentText
Rectangle {
id: inputConfiguration
anchors.fill: parent
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: 230
HifiConstants { id: hifi }
@ -168,7 +173,7 @@ Item {
text: "show all input devices"
onClicked: {
inputPlugins();
box.model = inputPlugins();
changeSource();
}
}
@ -208,25 +213,28 @@ Item {
anchors.leftMargin: 10
anchors.topMargin: 30
}
}
Rectangle {
id: loaderRectangle
z: -1
color: hifi.colors.baseGray
width: parent.width
anchors.left: parent.left
anchors.right: parent.right
anchors.top: inputConfiguration.bottom
anchors.bottom: parent.bottom
Loader {
id: loader
asynchronous: false
width: inputConfiguration.width
anchors.left: inputConfiguration.left
anchors.right: inputConfiguration.right
anchors.top: configurationHeader.bottom
anchors.topMargin: 10
anchors.bottom: inputConfiguration.bottom
source: InputConfiguration.configurationLayout(box.currentText);
anchors.fill: parent
source: InputConfiguration.configurationLayout(box.textAt(box.currentIndex));
onLoaded: {
if (loader.item.hasOwnProperty("pluginName")) {
if (box.currentText === "HTC Vive") {
if (box.textAt(box.currentIndex) === "HTC Vive") {
loader.item.pluginName = "OpenVR";
} else {
loader.item.pluginName = box.currentText;
loader.item.pluginName = box.textAt(box.currentIndex);
}
}
@ -252,11 +260,12 @@ Item {
function changeSource() {
loader.source = "";
var selectedDevice = box.textAt(box.currentIndex);
var source = "";
if (box.currentText == "Vive") {
if (selectedDevice == "HTC Vive") {
source = InputConfiguration.configurationLayout("OpenVR");
} else {
source = InputConfiguration.configurationLayout(box.currentText);
source = InputConfiguration.configurationLayout(selectedDevice);
}
loader.source = source;

View file

@ -24,6 +24,7 @@ Rectangle {
color: hifi.colors.baseGray;
signal sendToScript(var message);
property bool keyboardEnabled: false
property bool keyboardRaised: false
property bool punctuationMode: false
property bool keyboardRasied: false
@ -235,10 +236,11 @@ Rectangle {
Keyboard {
id: keyboard
raised: parent.keyboardEnabled
raised: parent.keyboardEnabled && parent.keyboardRaised
numeric: parent.punctuationMode
anchors {
bottom: parent.bottom
bottomMargin: 40
left: parent.left
right: parent.right
}

File diff suppressed because it is too large Load diff

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
@ -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 {
@ -287,6 +291,7 @@ Rectangle {
}
onFolderChanged: {
d.clearSelection();
fileTableModel.update(); // Update once the data from the folder change is available.
}
@ -327,7 +332,12 @@ Rectangle {
}
}
ListModel {
Component {
id: filesModelBuilder
ListModel { }
}
QtObject {
id: fileTableModel
// FolderListModel has a couple of problems:
@ -379,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() {
@ -397,7 +411,7 @@ Rectangle {
rows = 0,
i;
clear();
filesModel = filesModelBuilder.createObject(root);
comparisonFunction = sortOrder === Qt.AscendingOrder
? function(a, b) { return a < b; }
@ -419,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 {
@ -428,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"),
@ -439,8 +453,6 @@ Rectangle {
rows++;
}
d.clearSelection();
}
}
@ -465,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(); }
@ -522,7 +534,7 @@ Rectangle {
}
}
TableViewColumn {
QQC1.TableViewColumn {
id: fileNameColumn
role: "fileName"
title: "Name"
@ -530,7 +542,7 @@ Rectangle {
movable: false
resizable: true
}
TableViewColumn {
QQC1.TableViewColumn {
id: fileMofifiedColumn
role: "fileModified"
title: "Date"
@ -539,7 +551,7 @@ Rectangle {
resizable: true
visible: !selectDirectory
}
TableViewColumn {
QQC1.TableViewColumn {
role: "fileSize"
title: "Size"
width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width
@ -554,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();
}
@ -573,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

@ -15,6 +15,17 @@
#include <QObject>
/**jsdoc
* @namespace HifiAbout
*
* @hifi-interface
* @hifi-client-entity
*
* @property {string} buildDate
* @property {string} buildVersion
* @property {string} qtVersion
*/
class AboutUtil : public QObject {
Q_OBJECT
@ -30,6 +41,11 @@ public:
QString getQtVersion() const;
public slots:
/**jsdoc
* @function HifiAbout.openUrl
* @param {string} url
*/
void openUrl(const QString &url) const;
private:
AboutUtil(QObject* parent = nullptr);

View file

@ -198,7 +198,6 @@
#include <GPUIdent.h>
#include <gl/GLHelpers.h>
#include <src/scripting/LimitlessVoiceRecognitionScriptingInterface.h>
#include <src/scripting/GooglePolyScriptingInterface.h>
#include <EntityScriptClient.h>
#include <ModelScriptingInterface.h>
@ -697,8 +696,8 @@ private:
};
/**jsdoc
* <p>The <code>Controller.Hardware.Application</code> object has properties representing Interface's state. The property
* values are integer IDs, uniquely identifying each output. <em>Read-only.</em> These can be mapped to actions or functions or
* <p>The <code>Controller.Hardware.Application</code> object has properties representing Interface's state. The property
* values are integer IDs, uniquely identifying each output. <em>Read-only.</em> These can be mapped to actions or functions or
* <code>Controller.Standard</code> items in a {@link RouteObject} mapping (e.g., using the {@link RouteObject#when} method).
* Each data value is either <code>1.0</code> for "true" or <code>0.0</code> for "false".</p>
* <table>
@ -780,7 +779,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]);
@ -802,15 +801,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");
}
@ -818,7 +810,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();
@ -913,7 +904,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<DiscoverabilityManager>();
DependencyManager::set<SceneScriptingInterface>();
DependencyManager::set<OffscreenUi>();
DependencyManager::set<AutoUpdater>();
DependencyManager::set<Midi>();
DependencyManager::set<PathUtils>();
DependencyManager::set<InterfaceDynamicFactory>();
@ -930,7 +920,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<OffscreenQmlSurfaceCache>();
DependencyManager::set<EntityScriptClient>();
DependencyManager::set<EntityScriptServerLogClient>();
DependencyManager::set<LimitlessVoiceRecognitionScriptingInterface>();
DependencyManager::set<GooglePolyScriptingInterface>();
DependencyManager::set<OctreeStatsProvider>(nullptr, qApp->getOcteeSceneStats());
DependencyManager::set<AvatarBookmarks>();
@ -1116,7 +1105,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.";
@ -1373,11 +1362,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.");
@ -1421,7 +1410,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";
@ -1447,17 +1436,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.
@ -1469,6 +1450,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() },
@ -1791,10 +1773,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// If launched from Steam, let it handle updates
const QString HIFI_NO_UPDATER_COMMAND_LINE_KEY = "--no-updater";
bool noUpdater = arguments().indexOf(HIFI_NO_UPDATER_COMMAND_LINE_KEY) != -1;
if (!noUpdater) {
bool buildCanUpdate = BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable
|| BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Master;
if (!noUpdater && buildCanUpdate) {
constexpr auto INSTALLER_TYPE_CLIENT_ONLY = "client_only";
auto applicationUpdater = DependencyManager::get<AutoUpdater>();
auto applicationUpdater = DependencyManager::set<AutoUpdater>();
AutoUpdater::InstallerType type = installerType == INSTALLER_TYPE_CLIENT_ONLY
? AutoUpdater::InstallerType::CLIENT_ONLY : AutoUpdater::InstallerType::FULL;
@ -2186,7 +2170,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);
@ -2257,9 +2241,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
DependencyManager::get<EntityTreeRenderer>()->setSetPrecisionPickingOperator([&](unsigned int rayPickID, bool value) {
DependencyManager::get<PickManager>()->setPrecisionPicking(rayPickID, value);
});
EntityTreeRenderer::setRenderDebugHullsOperator([] {
return Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls);
});
// Preload Tablet sounds
DependencyManager::get<TabletScriptingInterface>()->preloadSounds();
@ -2403,7 +2384,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());
@ -2616,10 +2597,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();
@ -2631,6 +2657,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()) {
@ -2640,7 +2671,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>();
@ -2663,7 +2694,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);
@ -2676,7 +2707,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
@ -2753,40 +2784,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();
@ -3677,9 +3674,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:
@ -3808,68 +3817,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;
@ -4252,7 +4199,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;
}
@ -4818,12 +4765,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())) {
@ -5498,7 +5448,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();
}
@ -5877,7 +5827,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();
@ -5938,14 +5888,10 @@ void Application::update(float deltaTime) {
{
PerformanceTimer perfTimer("limitless");
PerformanceTimer perfTimer("AnimDebugDraw");
AnimDebugDraw::getInstance().update();
}
{
PerformanceTimer perfTimer("limitless");
DependencyManager::get<LimitlessVoiceRecognitionScriptingInterface>()->update();
}
{ // Game loop is done, mark the end of the frame for the scene transactions and the render loop to take over
PerformanceTimer perfTimer("enqueueFrame");
@ -6245,7 +6191,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)";
@ -6653,7 +6601,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
scriptEngine->registerGlobalObject("Users", DependencyManager::get<UsersScriptingInterface>().data());
scriptEngine->registerGlobalObject("LimitlessSpeechRecognition", DependencyManager::get<LimitlessVoiceRecognitionScriptingInterface>().data());
scriptEngine->registerGlobalObject("GooglePoly", DependencyManager::get<GooglePolyScriptingInterface>().data());
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
@ -7807,7 +7754,7 @@ void Application::sendLambdaEvent(const std::function<void()>& f) {
} else {
LambdaEvent event(f);
QCoreApplication::sendEvent(this, &event);
}
}
}
void Application::initPlugins(const QStringList& arguments) {
@ -8030,7 +7977,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>();
@ -8139,7 +8086,6 @@ void Application::switchDisplayMode() {
setActiveDisplayPlugin(DESKTOP_DISPLAY_PLUGIN_NAME);
startHMDStandBySession();
}
emit activeDisplayPluginChanged();
}
_previousHMDWornStatus = currentHMDWornStatus;
}

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();
@ -208,10 +205,6 @@ void Application::runRenderFrame(RenderArgs* renderArgs) {
RenderArgs::DebugFlags renderDebugFlags = RenderArgs::RENDER_DEBUG_NONE;
if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls)) {
renderDebugFlags = static_cast<RenderArgs::DebugFlags>(renderDebugFlags |
static_cast<int>(RenderArgs::RENDER_DEBUG_HULLS));
}
renderArgs->_debugFlags = renderDebugFlags;
}

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

@ -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,
@ -705,7 +725,6 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned,
0, false, drawStatusConfig, SLOT(setShowNetwork(bool)));
}
addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowHulls, 0, false, qApp->getEntities().data(), SIGNAL(setRenderDebugHulls()));
addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletWireframe, 0, false, qApp, SLOT(setShowBulletWireframe(bool)));
addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletAABBs, 0, false, qApp, SLOT(setShowBulletAABBs(bool)));
@ -793,7 +812,7 @@ Menu::Menu() {
});
essLogAction->setEnabled(nodeList->getThisNodeCanRez());
action = addActionToQMenuAndActionHash(developerMenu, "Script Log (HMD friendly)...", Qt::NoButton,
addActionToQMenuAndActionHash(developerMenu, "Script Log (HMD friendly)...", Qt::NoButton,
qApp, SLOT(showScriptLogs()));
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::VerboseLogging, 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";
@ -140,7 +141,6 @@ namespace MenuOption {
const QString Overlays = "Show Overlays";
const QString PackageModel = "Package Model as .fst...";
const QString Pair = "Pair";
const QString PhysicsShowHulls = "Draw Collision Shapes";
const QString PhysicsShowOwned = "Highlight Simulation Ownership";
const QString VerboseLogging = "Verbose Logging";
const QString PhysicsShowBulletWireframe = "Show Bullet Collision";
@ -202,6 +202,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

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

View file

@ -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
@ -165,8 +145,3 @@ void AvatarMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& ma
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;
@ -67,8 +64,6 @@ public:
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
virtual float getMass() 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;
@ -1199,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));
@ -2086,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
@ -2399,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;
@ -2412,6 +2473,8 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings
setModelScale(_targetScale);
rebuildCollisionShape();
settings.endGroup();
_haveReceivedHeightLimitsFromDomain = true;
}
void MyAvatar::leaveDomain() {
@ -2429,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) {
@ -2546,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
}
@ -2556,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) {
@ -2583,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;
@ -2609,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.
@ -2808,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);
@ -2830,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;
@ -2839,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();
}
@ -2860,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;
}
@ -3003,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 {
@ -3076,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);
@ -3476,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.
@ -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
@ -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;
@ -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

@ -104,7 +104,7 @@ public:
* @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.
@ -118,7 +118,7 @@ public:
* @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

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

@ -56,7 +56,8 @@ class QScriptEngine;
* @property {Uuid} tabletID - The UUID of the tablet body model overlay.
* @property {Uuid} tabletScreenID - The UUID of the tablet's screen overlay.
* @property {Uuid} homeButtonID - The UUID of the tablet's "home" button overlay.
* @property {Uuid} homeButtonHighlightID - The UUID of the tablet's "home" button highlight overlay.
* @property {Uuid} homeButtonHighlightMaterialID - The UUID of the material entity used to highlight tablet button
* @property {Uuid} homeButtonUnhighlightMaterialID - The UUID of the material entity use to unhighlight the entity
*/
class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency {
Q_OBJECT
@ -67,8 +68,9 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen
Q_PROPERTY(bool tabletContextualMode READ getTabletContextualMode)
Q_PROPERTY(QUuid tabletID READ getCurrentTabletFrameID WRITE setCurrentTabletFrameID)
Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonID WRITE setCurrentHomeButtonID)
Q_PROPERTY(QUuid homeButtonHighlightID READ getCurrentHomeButtonHightlightID WRITE setCurrentHomeButtonHightlightID)
Q_PROPERTY(QUuid tabletScreenID READ getCurrentTabletScreenID WRITE setCurrentTabletScreenID)
Q_PROPERTY(QUuid homeButtonHighlightMaterialID READ getCurrentHomeButtonHighlightMaterialID WRITE setCurrentHomeButtonHighlightMaterialID)
Q_PROPERTY(QUuid homeButtonUnhighlightMaterialID READ getCurrentHomeButtonUnhighlightMaterialID WRITE setCurrentHomeButtonUnhighlightMaterialID)
public:
@ -372,20 +374,24 @@ public:
void setCurrentHomeButtonID(QUuid homeButtonID) { _homeButtonID = homeButtonID; }
QUuid getCurrentHomeButtonID() const { return _homeButtonID; }
void setCurrentHomeButtonHightlightID(QUuid homeButtonHightlightID) { _homeButtonHightlightID = homeButtonHightlightID; }
QUuid getCurrentHomeButtonHightlightID() const { return _homeButtonHightlightID; }
void setCurrentTabletScreenID(QUuid tabletID) { _tabletScreenID = tabletID; }
QUuid getCurrentTabletScreenID() const { return _tabletScreenID; }
void setCurrentHomeButtonHighlightMaterialID(QUuid homeButtonHighlightMaterialID) { _homeButtonHighlightMaterialID = homeButtonHighlightMaterialID; }
QUuid getCurrentHomeButtonHighlightMaterialID() { return _homeButtonHighlightMaterialID; }
void setCurrentHomeButtonUnhighlightMaterialID(QUuid homeButtonUnhighlightMaterialID) { _homeButtonUnhighlightMaterialID = homeButtonUnhighlightMaterialID; }
QUuid getCurrentHomeButtonUnhighlightMaterialID() { return _homeButtonUnhighlightMaterialID; }
private:
bool _showTablet { false };
bool _tabletContextualMode { false };
QUuid _tabletUIID; // this is the entityID of the tablet frame
QUuid _tabletScreenID; // this is the overlayID which is part of (a child of) the tablet-ui.
QUuid _homeButtonID;
QUuid _homeButtonHightlightID;
QUuid _tabletEntityID;
QUuid _homeButtonHighlightMaterialID;
QUuid _homeButtonUnhighlightMaterialID;
// Get the position of the HMD
glm::vec3 getPosition() const;

View file

@ -1,93 +0,0 @@
#include "LimitlessConnection.h"
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include <src/InterfaceLogging.h>
#include <src/ui/AvatarInputs.h>
#include "LimitlessVoiceRecognitionScriptingInterface.h"
LimitlessConnection::LimitlessConnection() :
_streamingAudioForTranscription(false)
{
}
void LimitlessConnection::startListening(QString authCode) {
_transcribeServerSocket.reset(new QTcpSocket(this));
connect(_transcribeServerSocket.get(), &QTcpSocket::readyRead, this,
&LimitlessConnection::transcriptionReceived);
connect(_transcribeServerSocket.get(), &QTcpSocket::disconnected, this, [this](){stopListening();});
static const auto host = "gserv_devel.studiolimitless.com";
_transcribeServerSocket->connectToHost(host, 1407);
_transcribeServerSocket->waitForConnected();
QString requestHeader = QString::asprintf("Authorization: %s\r\nfs: %i\r\n",
authCode.toLocal8Bit().data(), AudioConstants::SAMPLE_RATE);
qCDebug(interfaceapp) << "Sending Limitless Audio Stream Request: " << requestHeader;
_transcribeServerSocket->write(requestHeader.toLocal8Bit());
_transcribeServerSocket->waitForBytesWritten();
}
void LimitlessConnection::stopListening() {
emit onFinishedSpeaking(_currentTranscription);
_streamingAudioForTranscription = false;
_currentTranscription = "";
if (!isConnected())
return;
_transcribeServerSocket->close();
disconnect(_transcribeServerSocket.get(), &QTcpSocket::readyRead, this,
&LimitlessConnection::transcriptionReceived);
_transcribeServerSocket.release()->deleteLater();
disconnect(DependencyManager::get<AudioClient>().data(), &AudioClient::inputReceived, this,
&LimitlessConnection::audioInputReceived);
qCDebug(interfaceapp) << "Connection to Limitless Voice Server closed.";
}
void LimitlessConnection::audioInputReceived(const QByteArray& inputSamples) {
if (isConnected()) {
_transcribeServerSocket->write(inputSamples.data(), inputSamples.size());
_transcribeServerSocket->waitForBytesWritten();
}
}
void LimitlessConnection::transcriptionReceived() {
while (_transcribeServerSocket && _transcribeServerSocket->bytesAvailable() > 0) {
const QByteArray data = _transcribeServerSocket->readAll();
_serverDataBuffer.append(data);
int begin = _serverDataBuffer.indexOf('<');
int end = _serverDataBuffer.indexOf('>');
while (begin > -1 && end > -1) {
const int len = end - begin;
const QByteArray serverMessage = _serverDataBuffer.mid(begin+1, len-1);
if (serverMessage.contains("1407")) {
qCDebug(interfaceapp) << "Limitless Speech Server denied the request.";
// Don't spam the server with further false requests please.
DependencyManager::get<LimitlessVoiceRecognitionScriptingInterface>()->setListeningToVoice(true);
stopListening();
return;
} else if (serverMessage.contains("1408")) {
qCDebug(interfaceapp) << "Limitless Audio request authenticated!";
_serverDataBuffer.clear();
connect(DependencyManager::get<AudioClient>().data(), &AudioClient::inputReceived, this,
&LimitlessConnection::audioInputReceived);
return;
}
QJsonObject json = QJsonDocument::fromJson(serverMessage.data()).object();
_serverDataBuffer.remove(begin, len+1);
_currentTranscription = json["alternatives"].toArray()[0].toObject()["transcript"].toString();
emit onReceivedTranscription(_currentTranscription);
if (json["isFinal"] == true) {
qCDebug(interfaceapp) << "Final transcription: " << _currentTranscription;
stopListening();
return;
}
begin = _serverDataBuffer.indexOf('<');
end = _serverDataBuffer.indexOf('>');
}
}
}
bool LimitlessConnection::isConnected() const {
return _transcribeServerSocket.get() && _transcribeServerSocket->isWritable()
&& _transcribeServerSocket->state() != QAbstractSocket::SocketState::UnconnectedState;
}

View file

@ -1,46 +0,0 @@
//
// SpeechRecognitionScriptingInterface.h
// interface/src/scripting
//
// Created by Trevor Berninger on 3/24/17.
// Copyright 2017 Limitless ltd.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_LimitlessConnection_h
#define hifi_LimitlessConnection_h
#include <QtCore/QObject>
#include <QtCore/QFuture>
#include <QtNetwork/QTcpSocket>
#include <AudioClient.h>
class LimitlessConnection : public QObject {
Q_OBJECT
public:
LimitlessConnection();
Q_INVOKABLE void startListening(QString authCode);
Q_INVOKABLE void stopListening();
std::atomic<bool> _streamingAudioForTranscription;
signals:
void onReceivedTranscription(QString speech);
void onFinishedSpeaking(QString speech);
private:
void transcriptionReceived();
void audioInputReceived(const QByteArray& inputSamples);
bool isConnected() const;
std::unique_ptr<QTcpSocket> _transcribeServerSocket;
QByteArray _serverDataBuffer;
QString _currentTranscription;
};
#endif //hifi_LimitlessConnection_h

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