mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-07 23:12:36 +02:00
Merge remote-tracking branch 'upstream/master' into breakpad_android
This commit is contained in:
commit
583c46b3a0
229 changed files with 15300 additions and 5202 deletions
|
@ -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.
|
||||
|
||||
|
|
|
@ -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',
|
||||
'-DUSE_BREAKPAD=' + (project.hasProperty("BACKTRACE_URL") && project.hasProperty("BACKTRACE_TOKEN") ? 'ON' : 'OFF');
|
||||
|
@ -135,4 +138,3 @@ dependencies {
|
|||
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,11 @@ public class HomeFragment extends Fragment {
|
|||
mDomainsView.setLayoutManager(gridLayoutMgr);
|
||||
mDomainAdapter = new DomainAdapter(getContext(), HifiUtils.getInstance().protocolVersionSignature(), nativeGetLastLocation());
|
||||
mDomainAdapter.setClickListener((view, position, domain) -> {
|
||||
new Handler(getActivity().getMainLooper()).postDelayed(() -> mListener.onSelectedDomain(domain.url), 400); // a delay so the ripple effect can be seen
|
||||
new Handler(getActivity().getMainLooper()).postDelayed(() -> {
|
||||
if (mListener != null) {
|
||||
mListener.onSelectedDomain(domain.url);
|
||||
}
|
||||
}, 400); // a delay so the ripple effect can be seen
|
||||
});
|
||||
mDomainAdapter.setListener(new DomainAdapter.AdapterListener() {
|
||||
@Override
|
||||
|
@ -116,7 +120,9 @@ public class HomeFragment extends Fragment {
|
|||
if (!urlString.trim().isEmpty()) {
|
||||
urlString = HifiUtils.getInstance().sanitizeHifiUrl(urlString);
|
||||
}
|
||||
mListener.onSelectedDomain(urlString);
|
||||
if (mListener != null) {
|
||||
mListener.onSelectedDomain(urlString);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package io.highfidelity.hifiinterface.provider;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.MutableInt;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import io.highfidelity.hifiinterface.HifiUtils;
|
||||
import io.highfidelity.hifiinterface.view.DomainAdapter;
|
||||
|
@ -47,24 +48,42 @@ public class UserStoryDomainProvider implements DomainProvider {
|
|||
suggestions = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void retrieve(String filterText, DomainCallback domainCallback) {
|
||||
if (!startedToGetFromAPI) {
|
||||
startedToGetFromAPI = true;
|
||||
fillDestinations(filterText, domainCallback);
|
||||
} else {
|
||||
filterChoicesByText(filterText, domainCallback);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillDestinations(String filterText, DomainCallback domainCallback) {
|
||||
StoriesFilter filter = new StoriesFilter(filterText);
|
||||
final MutableInt counter = new MutableInt(0);
|
||||
allStories.clear();
|
||||
getUserStoryPage(1,
|
||||
|
||||
List<UserStory> taggedStories = new ArrayList<>();
|
||||
Set<String> taggedStoriesIds = new HashSet<>();
|
||||
getUserStoryPage(1, taggedStories, TAGS_TO_SEARCH,
|
||||
e -> {
|
||||
allStories.subList(counter.value, allStories.size()).forEach(userStory -> {
|
||||
filter.filterOrAdd(userStory);
|
||||
});
|
||||
if (domainCallback != null) {
|
||||
domainCallback.retrieveOk(suggestions); //ended
|
||||
}
|
||||
},
|
||||
a -> {
|
||||
allStories.forEach(userStory -> {
|
||||
counter.value++;
|
||||
filter.filterOrAdd(userStory);
|
||||
taggedStories.forEach(userStory -> {
|
||||
taggedStoriesIds.add(userStory.id);
|
||||
});
|
||||
|
||||
allStories.clear();
|
||||
getUserStoryPage(1, allStories, null,
|
||||
ex -> {
|
||||
allStories.forEach(userStory -> {
|
||||
if (taggedStoriesIds.contains(userStory.id)) {
|
||||
userStory.tagFound = true;
|
||||
}
|
||||
filter.filterOrAdd(userStory);
|
||||
});
|
||||
if (domainCallback != null) {
|
||||
domainCallback.retrieveOk(suggestions); //ended
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -73,25 +92,22 @@ public class UserStoryDomainProvider implements DomainProvider {
|
|||
restOfPagesCallback.callback(new Exception("Error accessing url [" + url + "]", t));
|
||||
}
|
||||
|
||||
private void getUserStoryPage(int pageNumber, Callback<Exception> restOfPagesCallback, Callback<Void> firstPageCallback) {
|
||||
private void getUserStoryPage(int pageNumber, List<UserStory> userStoriesList, String tagsFilter, Callback<Exception> restOfPagesCallback) {
|
||||
Call<UserStories> userStories = mUserStoryDomainProviderService.getUserStories(
|
||||
INCLUDE_ACTIONS_FOR_PLACES,
|
||||
"open",
|
||||
true,
|
||||
mProtocol,
|
||||
TAGS_TO_SEARCH,
|
||||
tagsFilter,
|
||||
pageNumber);
|
||||
Log.d("API-USER-STORY-DOMAINS", "Protocol [" + mProtocol + "] include_actions [" + INCLUDE_ACTIONS_FOR_PLACES + "]");
|
||||
userStories.enqueue(new retrofit2.Callback<UserStories>() {
|
||||
@Override
|
||||
public void onResponse(Call<UserStories> call, Response<UserStories> response) {
|
||||
UserStories data = response.body();
|
||||
allStories.addAll(data.user_stories);
|
||||
userStoriesList.addAll(data.user_stories);
|
||||
if (data.current_page < data.total_pages && data.current_page <= MAX_PAGES_TO_GET) {
|
||||
if (pageNumber == 1 && firstPageCallback != null) {
|
||||
firstPageCallback.callback(null);
|
||||
}
|
||||
getUserStoryPage(pageNumber + 1, restOfPagesCallback, null);
|
||||
getUserStoryPage(pageNumber + 1, userStoriesList, tagsFilter, restOfPagesCallback);
|
||||
return;
|
||||
}
|
||||
restOfPagesCallback.callback(null);
|
||||
|
@ -107,12 +123,16 @@ public class UserStoryDomainProvider implements DomainProvider {
|
|||
private class StoriesFilter {
|
||||
String[] mWords = new String[]{};
|
||||
public StoriesFilter(String filterText) {
|
||||
mWords = filterText.toUpperCase().split("\\s+");
|
||||
mWords = filterText.trim().toUpperCase().split("\\s+");
|
||||
if (mWords.length == 1 && (mWords[0] == null || mWords[0].length() <= 0 ) ) {
|
||||
mWords = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean matches(UserStory story) {
|
||||
if (mWords.length <= 0) {
|
||||
return true;
|
||||
if (mWords == null || mWords.length <= 0) {
|
||||
// No text filter? So filter by tag
|
||||
return story.tagFound;
|
||||
}
|
||||
|
||||
for (String word : mWords) {
|
||||
|
@ -128,6 +148,9 @@ public class UserStoryDomainProvider implements DomainProvider {
|
|||
suggestions.add(story.toDomain());
|
||||
}
|
||||
|
||||
/**
|
||||
* if the story matches this filter criteria it's added into suggestions
|
||||
* */
|
||||
public void filterOrAdd(UserStory story) {
|
||||
if (matches(story)) {
|
||||
addToSuggestions(story);
|
||||
|
@ -144,16 +167,6 @@ public class UserStoryDomainProvider implements DomainProvider {
|
|||
domainCallback.retrieveOk(suggestions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void retrieve(String filterText, DomainCallback domainCallback) {
|
||||
if (!startedToGetFromAPI) {
|
||||
startedToGetFromAPI = true;
|
||||
fillDestinations(filterText, domainCallback);
|
||||
} else {
|
||||
filterChoicesByText(filterText, domainCallback);
|
||||
}
|
||||
}
|
||||
|
||||
public interface UserStoryDomainProviderService {
|
||||
@GET("api/v1/user_stories")
|
||||
Call<UserStories> getUserStories(@Query("include_actions") String includeActions,
|
||||
|
@ -166,12 +179,14 @@ public class UserStoryDomainProvider implements DomainProvider {
|
|||
|
||||
class UserStory {
|
||||
public UserStory() {}
|
||||
String id;
|
||||
String place_name;
|
||||
String path;
|
||||
String thumbnail_url;
|
||||
String place_id;
|
||||
String domain_id;
|
||||
private String searchText;
|
||||
private boolean tagFound; // Locally used
|
||||
|
||||
// New fields? tags, description
|
||||
|
||||
|
|
|
@ -54,27 +54,10 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
|
|||
@Override
|
||||
public void retrieveOk(List<Domain> domain) {
|
||||
if (filterText.length() == 0) {
|
||||
Domain lastVisitedDomain = new Domain(mContext.getString(R.string.your_last_location), mLastLocation, DEFAULT_THUMBNAIL_PLACE);
|
||||
if (!mLastLocation.isEmpty() && mLastLocation.contains("://")) {
|
||||
int startIndex = mLastLocation.indexOf("://");
|
||||
int endIndex = mLastLocation.indexOf("/", startIndex + 3);
|
||||
String toSearch = mLastLocation.substring(0, endIndex + 1).toLowerCase();
|
||||
for (Domain d : domain) {
|
||||
if (d.url.toLowerCase().startsWith(toSearch)) {
|
||||
lastVisitedDomain.thumbnail = d.thumbnail;
|
||||
}
|
||||
}
|
||||
}
|
||||
domain.add(0, lastVisitedDomain);
|
||||
addLastLocation(domain);
|
||||
}
|
||||
|
||||
for (Domain d : domain) {
|
||||
// we override the default picture added in the server by an android specific version
|
||||
if (d.thumbnail != null &&
|
||||
d.thumbnail.endsWith("assets/places/thumbnail-default-place-e5a3f33e773ab699495774990a562f9f7693dc48ef90d8be6985c645a0280f75.png")) {
|
||||
d.thumbnail = DEFAULT_THUMBNAIL_PLACE;
|
||||
}
|
||||
}
|
||||
overrideDefaultThumbnails(domain);
|
||||
|
||||
mDomains = new Domain[domain.size()];
|
||||
mDomains = domain.toArray(mDomains);
|
||||
|
@ -96,6 +79,31 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
|
|||
});
|
||||
}
|
||||
|
||||
private void overrideDefaultThumbnails(List<Domain> domain) {
|
||||
for (Domain d : domain) {
|
||||
// we override the default picture added in the server by an android specific version
|
||||
if (d.thumbnail != null &&
|
||||
d.thumbnail.endsWith("assets/places/thumbnail-default-place-e5a3f33e773ab699495774990a562f9f7693dc48ef90d8be6985c645a0280f75.png")) {
|
||||
d.thumbnail = DEFAULT_THUMBNAIL_PLACE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addLastLocation(List<Domain> domain) {
|
||||
Domain lastVisitedDomain = new Domain(mContext.getString(R.string.your_last_location), mLastLocation, DEFAULT_THUMBNAIL_PLACE);
|
||||
if (!mLastLocation.isEmpty() && mLastLocation.contains("://")) {
|
||||
int startIndex = mLastLocation.indexOf("://");
|
||||
int endIndex = mLastLocation.indexOf("/", startIndex + 3);
|
||||
String toSearch = mLastLocation.substring(0, endIndex + 1).toLowerCase();
|
||||
for (Domain d : domain) {
|
||||
if (d.url.toLowerCase().startsWith(toSearch)) {
|
||||
lastVisitedDomain.thumbnail = d.thumbnail;
|
||||
}
|
||||
}
|
||||
}
|
||||
domain.add(0, lastVisitedDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = mInflater.inflate(R.layout.domain_view, parent, false);
|
||||
|
|
|
@ -37,7 +37,7 @@ task clean(type: Delete) {
|
|||
ext {
|
||||
RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0'
|
||||
RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV'
|
||||
BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : ''
|
||||
STABLE_BUILD = project.hasProperty('STABLE_BUILD') ? project.getProperty('STABLE_BUILD') : '0'
|
||||
EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : ''
|
||||
QT5_DEPS = [
|
||||
'Qt5Concurrent',
|
||||
|
@ -551,7 +551,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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -624,8 +624,8 @@ AudioMixerClientData::IgnoreZone& AudioMixerClientData::IgnoreZoneMemo::get(unsi
|
|||
scale = MIN_IGNORE_BOX_SCALE;
|
||||
}
|
||||
|
||||
// quadruple the scale (this is arbitrary number chosen for comfort)
|
||||
const float IGNORE_BOX_SCALE_FACTOR = 4.0f;
|
||||
// (this is arbitrary number determined empirically for comfort)
|
||||
const float IGNORE_BOX_SCALE_FACTOR = 2.4f;
|
||||
scale *= IGNORE_BOX_SCALE_FACTOR;
|
||||
|
||||
// create the box (we use a box for the zone for convenience)
|
||||
|
|
|
@ -271,8 +271,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) {
|
||||
otherNodeBox.setScaleStayCentered(minBubbleSize);
|
||||
}
|
||||
// Quadruple the scale of both bounding boxes
|
||||
otherNodeBox.embiggen(4.0f);
|
||||
// Change the scale of both bounding boxes
|
||||
// (This is an arbitrary number determined empirically)
|
||||
otherNodeBox.embiggen(2.4f);
|
||||
|
||||
// Perform the collision check between the two bounding boxes
|
||||
if (nodeBox.touches(otherNodeBox)) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -231,18 +231,19 @@ void OctreeServer::trackProcessWaitTime(float time) {
|
|||
OctreeServer::OctreeServer(ReceivedMessage& message) :
|
||||
ThreadedAssignment(message),
|
||||
_argc(0),
|
||||
_argv(NULL),
|
||||
_parsedArgV(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())
|
||||
{
|
||||
|
@ -265,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 << "]";
|
||||
|
@ -1052,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;
|
||||
|
@ -1128,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";
|
||||
|
@ -1288,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);
|
||||
|
@ -1384,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;
|
||||
|
||||
|
@ -1402,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...";
|
||||
|
|
|
@ -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;
|
||||
|
@ -190,7 +181,6 @@ protected:
|
|||
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;
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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@")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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@"
|
||||
|
|
|
@ -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@"
|
||||
}
|
||||
|
|
|
@ -34,8 +34,9 @@ static const chrono::minutes MAX_REFRESH_TIME { 5 };
|
|||
Q_DECLARE_LOGGING_CATEGORY(asset_backup)
|
||||
Q_LOGGING_CATEGORY(asset_backup, "hifi.asset-backup");
|
||||
|
||||
AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory) :
|
||||
_assetsDirectory(backupDirectory + ASSETS_DIR)
|
||||
AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory, bool assetServerEnabled) :
|
||||
_assetsDirectory(backupDirectory + ASSETS_DIR),
|
||||
_assetServerEnabled(assetServerEnabled)
|
||||
{
|
||||
// Make sure the asset directory exists.
|
||||
QDir(_assetsDirectory).mkpath(".");
|
||||
|
@ -53,6 +54,7 @@ void AssetsBackupHandler::setupRefreshTimer() {
|
|||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
QObject::connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, [this](SharedNodePointer node) {
|
||||
if (node->getType() == NodeType::AssetServer) {
|
||||
assert(_assetServerEnabled);
|
||||
// run immediately for the first time.
|
||||
_mappingsRefreshTimer.start(0);
|
||||
}
|
||||
|
@ -233,12 +235,12 @@ void AssetsBackupHandler::createBackup(const QString& backupName, QuaZip& zip) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (_lastMappingsRefresh.time_since_epoch().count() == 0) {
|
||||
if (_assetServerEnabled && _lastMappingsRefresh.time_since_epoch().count() == 0) {
|
||||
qCWarning(asset_backup) << "Current mappings not yet loaded.";
|
||||
return;
|
||||
}
|
||||
|
||||
if ((p_high_resolution_clock::now() - _lastMappingsRefresh) > MAX_REFRESH_TIME) {
|
||||
if (_assetServerEnabled && (p_high_resolution_clock::now() - _lastMappingsRefresh) > MAX_REFRESH_TIME) {
|
||||
qCWarning(asset_backup) << "Backing up asset mappings that might be stale.";
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ class AssetsBackupHandler : public QObject, public BackupHandlerInterface {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AssetsBackupHandler(const QString& backupDirectory);
|
||||
AssetsBackupHandler(const QString& backupDirectory, bool assetServerEnabled);
|
||||
|
||||
std::pair<bool, float> isAvailable(const QString& backupName) override;
|
||||
std::pair<bool, float> getRecoveryStatus() override;
|
||||
|
@ -65,6 +65,7 @@ private:
|
|||
void updateMappings();
|
||||
|
||||
QString _assetsDirectory;
|
||||
bool _assetServerEnabled { false };
|
||||
|
||||
QTimer _mappingsRefreshTimer;
|
||||
p_high_resolution_clock::time_point _lastMappingsRefresh;
|
||||
|
|
|
@ -176,7 +176,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
|
||||
qDebug() << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION;
|
||||
qDebug() << "[VERSION] VERSION:" << BuildInfo::VERSION;
|
||||
qDebug() << "[VERSION] BUILD_BRANCH:" << BuildInfo::BUILD_BRANCH;
|
||||
qDebug() << "[VERSION] BUILD_TYPE_STRING:" << BuildInfo::BUILD_TYPE_STRING;
|
||||
qDebug() << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES;
|
||||
qDebug() << "[VERSION] We will be using this name to find ICE servers:" << _iceServerAddr;
|
||||
|
||||
|
@ -307,7 +307,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){
|
||||
_contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath())));
|
||||
_contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir())));
|
||||
_contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir(), isAssetServerEnabled())));
|
||||
_contentManager->addBackupHandler(BackupHandlerPointer(new ContentSettingsBackupHandler(_settingsManager)));
|
||||
});
|
||||
|
||||
|
@ -991,15 +991,11 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
|
|||
defaultedType = static_cast<Assignment::Type>(static_cast<int>(defaultedType) + 1)) {
|
||||
if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) {
|
||||
|
||||
if (defaultedType == Assignment::AssetServerType) {
|
||||
// Make sure the asset-server is enabled before adding it here.
|
||||
// Initially we do not assign it by default so we can test it in HF domains first
|
||||
static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled";
|
||||
|
||||
if (!_settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool()) {
|
||||
// skip to the next iteration if asset-server isn't enabled
|
||||
continue;
|
||||
}
|
||||
// Make sure the asset-server is enabled before adding it here.
|
||||
// Initially we do not assign it by default so we can test it in HF domains first
|
||||
if (defaultedType == Assignment::AssetServerType && !isAssetServerEnabled()) {
|
||||
// skip to the next iteraion if asset-server isn't enabled
|
||||
continue;
|
||||
}
|
||||
|
||||
// type has not been set from a command line or config file config, use the default
|
||||
|
@ -1122,7 +1118,7 @@ void DomainServer::handleConnectedNode(SharedNodePointer newNode) {
|
|||
}
|
||||
|
||||
void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr) {
|
||||
const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NLPacket::NUM_BYTES_LOCALID +
|
||||
const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NLPacket::NUM_BYTES_LOCALID +
|
||||
NUM_BYTES_RFC4122_UUID + NLPacket::NUM_BYTES_LOCALID + 4;
|
||||
|
||||
// setup the extended header for the domain list packets
|
||||
|
@ -2684,7 +2680,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
QString settingsPassword = settingsPasswordVariant.isValid() ? settingsPasswordVariant.toString() : "";
|
||||
QString hexHeaderPassword = headerPassword.isEmpty() ?
|
||||
"" : QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
|
||||
|
||||
|
||||
if (settingsUsername == headerUsername && hexHeaderPassword == settingsPassword) {
|
||||
return true;
|
||||
}
|
||||
|
@ -2946,6 +2942,12 @@ bool DomainServer::shouldReplicateNode(const Node& node) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
bool DomainServer::isAssetServerEnabled() {
|
||||
static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled";
|
||||
return _settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool();
|
||||
}
|
||||
|
||||
void DomainServer::nodeAdded(SharedNodePointer node) {
|
||||
// we don't use updateNodeWithData, so add the DomainServerNodeData to the node here
|
||||
node->setLinkedData(std::unique_ptr<DomainServerNodeData> { new DomainServerNodeData() });
|
||||
|
|
|
@ -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);
|
||||
|
|
BIN
interface/resources/images/buttonBezel.png
Normal file
BIN
interface/resources/images/buttonBezel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
interface/resources/images/buttonBezel_highlight.png
Normal file
BIN
interface/resources/images/buttonBezel_highlight.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -2,88 +2,88 @@ name = mannequin
|
|||
type = body+head
|
||||
scale = 1
|
||||
filename = mannequin/mannequin.baked.fbx
|
||||
joint = jointRoot = Hips
|
||||
joint = jointNeck = Neck
|
||||
joint = jointLean = Spine
|
||||
joint = jointLeftHand = LeftHand
|
||||
joint = jointHead = Head
|
||||
joint = jointEyeLeft = LeftEye
|
||||
joint = jointEyeRight = RightEye
|
||||
joint = jointRoot = Hips
|
||||
joint = jointLeftHand = LeftHand
|
||||
joint = jointRightHand = RightHand
|
||||
joint = jointNeck = Neck
|
||||
joint = jointHead = Head
|
||||
freeJoint = LeftArm
|
||||
freeJoint = LeftForeArm
|
||||
freeJoint = RightArm
|
||||
freeJoint = RightForeArm
|
||||
bs = EyeBlink_L = blink = 1
|
||||
bs = JawOpen = mouth_Open = 1
|
||||
bs = LipsFunnel = Oo = 1
|
||||
bs = BrowsU_L = brow_Up = 1
|
||||
jointIndex = RightHandPinky2 = 19
|
||||
jointIndex = LeftHandMiddle4 = 61
|
||||
jointIndex = LeftHand = 41
|
||||
jointIndex = LeftHandRing4 = 49
|
||||
jointIndex = RightHandMiddle3 = 36
|
||||
jointIndex = LeftHandThumb4 = 57
|
||||
jointIndex = RightToe_End = 10
|
||||
jointIndex = LeftHandRing1 = 46
|
||||
jointIndex = LeftForeArm = 40
|
||||
jointIndex = RightHandIndex4 = 29
|
||||
jointIndex = LeftShoulder = 38
|
||||
jointIndex = RightHandMiddle4 = 37
|
||||
jointIndex = RightShoulder = 14
|
||||
jointIndex = LeftLeg = 2
|
||||
jointIndex = LeftToe_End = 5
|
||||
jointIndex = Hips = 0
|
||||
jointIndex = RightFoot = 8
|
||||
jointIndex = RightHandThumb2 = 31
|
||||
jointIndex = LeftHandMiddle3 = 60
|
||||
jointIndex = RightHandThumb1 = 30
|
||||
jointIndex = Neck = 62
|
||||
jointIndex = Spine = 11
|
||||
jointIndex = RightHandThumb4 = 33
|
||||
jointIndex = RightHandMiddle1 = 34
|
||||
jointIndex = LeftHandIndex4 = 53
|
||||
jointIndex = face = 68
|
||||
jointIndex = RightHandRing3 = 24
|
||||
jointIndex = LeftHandPinky4 = 45
|
||||
jointIndex = LeftHandMiddle2 = 59
|
||||
jointIndex = RightHandThumb3 = 32
|
||||
bs = EyeBlink_L = blink = 1
|
||||
jointIndex = LeftHandPinky3 = 44
|
||||
jointIndex = HeadTop_End = 66
|
||||
jointIndex = Spine1 = 12
|
||||
jointIndex = LeftHandRing3 = 48
|
||||
jointIndex = mannequin1 = 67
|
||||
jointIndex = RightEye = 65
|
||||
jointIndex = RightHandRing4 = 25
|
||||
jointIndex = RightHandPinky4 = 21
|
||||
jointIndex = LeftHandRing2 = 47
|
||||
jointIndex = RightHandIndex3 = 28
|
||||
jointIndex = RightUpLeg = 6
|
||||
jointIndex = LeftArm = 39
|
||||
jointIndex = LeftHandThumb3 = 56
|
||||
jointIndex = RightHandIndex2 = 27
|
||||
jointIndex = RightForeArm = 16
|
||||
jointIndex = RightArm = 15
|
||||
jointIndex = RightHandRing2 = 23
|
||||
jointIndex = LeftHandMiddle1 = 58
|
||||
jointIndex = Spine2 = 13
|
||||
jointIndex = LeftHandThumb2 = 55
|
||||
jointIndex = RightHandMiddle2 = 35
|
||||
jointIndex = RightHandPinky1 = 18
|
||||
jointIndex = LeftUpLeg = 1
|
||||
jointIndex = RightLeg = 7
|
||||
jointIndex = LeftHandIndex2 = 51
|
||||
jointIndex = LeftHand = 41
|
||||
jointIndex = RightHandMiddle1 = 34
|
||||
jointIndex = LeftHandPinky4 = 45
|
||||
jointIndex = RightHand = 17
|
||||
jointIndex = LeftHandIndex3 = 52
|
||||
jointIndex = LeftFoot = 3
|
||||
jointIndex = RightHandPinky3 = 20
|
||||
jointIndex = RightHandIndex1 = 26
|
||||
jointIndex = LeftHandPinky1 = 42
|
||||
jointIndex = RightToeBase = 9
|
||||
jointIndex = LeftHandIndex1 = 50
|
||||
jointIndex = LeftToeBase = 4
|
||||
jointIndex = LeftHandPinky2 = 43
|
||||
jointIndex = RightHandRing1 = 22
|
||||
jointIndex = LeftHandThumb1 = 54
|
||||
jointIndex = LeftEye = 64
|
||||
jointIndex = LeftFoot = 3
|
||||
jointIndex = Head = 63
|
||||
jointIndex = Spine1 = 12
|
||||
jointIndex = RightHandRing4 = 25
|
||||
jointIndex = RightHandPinky1 = 18
|
||||
jointIndex = LeftHandIndex1 = 50
|
||||
jointIndex = RightHandIndex3 = 28
|
||||
jointIndex = LeftHandIndex3 = 52
|
||||
jointIndex = LeftToe_End = 5
|
||||
jointIndex = RightArm = 15
|
||||
jointIndex = RightHandRing3 = 24
|
||||
jointIndex = RightHandThumb2 = 31
|
||||
jointIndex = Spine2 = 13
|
||||
jointIndex = HeadTop_End = 66
|
||||
jointIndex = LeftToeBase = 4
|
||||
jointIndex = RightUpLeg = 6
|
||||
jointIndex = RightForeArm = 16
|
||||
jointIndex = LeftHandMiddle1 = 58
|
||||
jointIndex = LeftHandRing3 = 48
|
||||
jointIndex = RightHandPinky4 = 21
|
||||
jointIndex = RightHandIndex1 = 26
|
||||
jointIndex = Hips = 0
|
||||
jointIndex = RightEye = 65
|
||||
jointIndex = RightHandPinky2 = 19
|
||||
jointIndex = LeftHandMiddle2 = 59
|
||||
jointIndex = LeftHandPinky1 = 42
|
||||
jointIndex = LeftHandRing4 = 49
|
||||
jointIndex = RightFoot = 8
|
||||
jointIndex = RightHandIndex2 = 27
|
||||
jointIndex = RightToe_End = 10
|
||||
jointIndex = RightHandThumb3 = 32
|
||||
jointIndex = LeftHandMiddle3 = 60
|
||||
jointIndex = LeftHandThumb4 = 57
|
||||
jointIndex = LeftHandMiddle4 = 61
|
||||
jointIndex = LeftHandThumb1 = 54
|
||||
jointIndex = LeftHandThumb3 = 56
|
||||
jointIndex = body = 67
|
||||
jointIndex = LeftArm = 39
|
||||
jointIndex = RightToeBase = 9
|
||||
jointIndex = LeftEye = 64
|
||||
jointIndex = RightLeg = 7
|
||||
jointIndex = face = 68
|
||||
jointIndex = LeftForeArm = 40
|
||||
jointIndex = RightHandThumb4 = 33
|
||||
jointIndex = RightHandRing1 = 22
|
||||
jointIndex = LeftUpLeg = 1
|
||||
jointIndex = LeftHandPinky2 = 43
|
||||
jointIndex = LeftLeg = 2
|
||||
jointIndex = LeftHandIndex4 = 53
|
||||
jointIndex = RightHandThumb1 = 30
|
||||
jointIndex = LeftHandRing2 = 47
|
||||
jointIndex = RightHandMiddle2 = 35
|
||||
jointIndex = RightHandMiddle3 = 36
|
||||
jointIndex = Spine = 11
|
||||
jointIndex = RightHandMiddle4 = 37
|
||||
jointIndex = LeftHandIndex2 = 51
|
||||
jointIndex = RightHandRing2 = 23
|
||||
jointIndex = LeftHandThumb2 = 55
|
||||
jointIndex = LeftShoulder = 38
|
||||
jointIndex = Neck = 62
|
||||
jointIndex = RightHandIndex4 = 29
|
||||
jointIndex = LeftHandRing1 = 46
|
||||
jointIndex = RightShoulder = 14
|
||||
|
|
BIN
interface/resources/meshes/mannequin/Eyes.png
Normal file
BIN
interface/resources/meshes/mannequin/Eyes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 1,002 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
interface/resources/meshes/mannequin/lambert1_Roughness.png
Normal file
BIN
interface/resources/meshes/mannequin/lambert1_Roughness.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 525 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -171,6 +171,10 @@ FocusScope {
|
|||
}
|
||||
}
|
||||
|
||||
function textAt(index) {
|
||||
return comboBox.textAt(index);
|
||||
}
|
||||
|
||||
HifiControls.Label {
|
||||
id: comboBoxLabel
|
||||
text: root.label
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import Qt.labs.settings 1.0
|
|||
import "../styles-uit"
|
||||
import "../controls-uit" as HifiControlsUit
|
||||
import "../controls" as HifiControls
|
||||
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
|
||||
|
||||
// references HMD, Users, UserActivityLogger from root context
|
||||
|
||||
|
@ -37,13 +38,42 @@ Rectangle {
|
|||
property var myData: ({profileUrl: "", displayName: "", userName: "", audioLevel: 0.0, avgAudioLevel: 0.0, admin: true, placeName: "", connection: "", isPresent: true}); // valid dummy until set
|
||||
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
|
||||
property var nearbyUserModelData: []; // This simple list is essentially a mirror of the nearbyUserModel listModel without all the extra complexities.
|
||||
property var connectionsUserModelData: []; // This simple list is essentially a mirror of the connectionsUserModel listModel without all the extra complexities.
|
||||
property bool iAmAdmin: false;
|
||||
property var activeTab: "nearbyTab";
|
||||
property bool currentlyEditingDisplayName: false
|
||||
property bool punctuationMode: false;
|
||||
|
||||
HifiConstants { id: hifi; }
|
||||
RootHttpRequest { id: http; }
|
||||
HifiModels.PSFListModel {
|
||||
id: connectionsUserModel;
|
||||
http: http;
|
||||
endpoint: "/api/v1/users?filter=connections";
|
||||
property var sortColumn: connectionsTable.getColumn(connectionsTable.sortIndicatorColumn);
|
||||
sortProperty: switch (sortColumn && sortColumn.role) {
|
||||
case 'placeName':
|
||||
'location';
|
||||
break;
|
||||
case 'connection':
|
||||
'is_friend';
|
||||
break;
|
||||
default:
|
||||
'username';
|
||||
}
|
||||
sortAscending: connectionsTable.sortIndicatorOrder === Qt.AscendingOrder;
|
||||
itemsPerPage: 9;
|
||||
listView: connectionsTable;
|
||||
processPage: function (data) {
|
||||
return data.users.map(function (user) {
|
||||
return {
|
||||
userName: user.username,
|
||||
connection: user.connection,
|
||||
profileUrl: user.images.thumbnail,
|
||||
placeName: (user.location.root || user.location.domain || {}).name || ''
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// The letterbox used for popup messages
|
||||
LetterboxMessage {
|
||||
|
@ -106,16 +136,6 @@ Rectangle {
|
|||
});
|
||||
return sessionIDs;
|
||||
}
|
||||
function getSelectedConnectionsUserNames() {
|
||||
var userNames = [];
|
||||
connectionsTable.selection.forEach(function (userIndex) {
|
||||
var datum = connectionsUserModelData[userIndex];
|
||||
if (datum) {
|
||||
userNames.push(datum.userName);
|
||||
}
|
||||
});
|
||||
return userNames;
|
||||
}
|
||||
function refreshNearbyWithFilter() {
|
||||
// We should just be able to set settings.filtered to inViewCheckbox.checked, but see #3249, so send to .js for saving.
|
||||
var userIds = getSelectedNearbySessionIDs();
|
||||
|
@ -232,9 +252,7 @@ Rectangle {
|
|||
anchors.fill: parent;
|
||||
onClicked: {
|
||||
if (activeTab != "connectionsTab") {
|
||||
connectionsLoading.visible = false;
|
||||
connectionsLoading.visible = true;
|
||||
pal.sendToScript({method: 'refreshConnections'});
|
||||
connectionsUserModel.getFirstPage();
|
||||
}
|
||||
activeTab = "connectionsTab";
|
||||
connectionsHelpText.color = hifi.colors.blueAccent;
|
||||
|
@ -258,11 +276,7 @@ Rectangle {
|
|||
id: reloadConnections;
|
||||
width: reloadConnections.height;
|
||||
glyph: hifi.glyphs.reload;
|
||||
onClicked: {
|
||||
connectionsLoading.visible = false;
|
||||
connectionsLoading.visible = true;
|
||||
pal.sendToScript({method: 'refreshConnections'});
|
||||
}
|
||||
onClicked: connectionsUserModel.getFirstPage('delayRefresh');
|
||||
}
|
||||
}
|
||||
// "CONNECTIONS" text
|
||||
|
@ -472,7 +486,7 @@ Rectangle {
|
|||
visible: !isCheckBox && !isButton && !isAvgAudio;
|
||||
uuid: model ? model.sessionId : "";
|
||||
selected: styleData.selected;
|
||||
isReplicated: model.isReplicated;
|
||||
isReplicated: model && model.isReplicated;
|
||||
isAdmin: model && model.admin;
|
||||
isPresent: model && model.isPresent;
|
||||
// Size
|
||||
|
@ -702,7 +716,7 @@ Rectangle {
|
|||
anchors.top: parent.top;
|
||||
anchors.topMargin: 185;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
visible: true;
|
||||
visible: !connectionsUserModel.retrievedAtLeastOnePage;
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
connectionsTimeoutTimer.start();
|
||||
|
@ -747,14 +761,6 @@ Rectangle {
|
|||
headerVisible: true;
|
||||
sortIndicatorColumn: settings.connectionsSortIndicatorColumn;
|
||||
sortIndicatorOrder: settings.connectionsSortIndicatorOrder;
|
||||
onSortIndicatorColumnChanged: {
|
||||
settings.connectionsSortIndicatorColumn = sortIndicatorColumn;
|
||||
sortConnectionsModel();
|
||||
}
|
||||
onSortIndicatorOrderChanged: {
|
||||
settings.connectionsSortIndicatorOrder = sortIndicatorOrder;
|
||||
sortConnectionsModel();
|
||||
}
|
||||
|
||||
TableViewColumn {
|
||||
id: connectionsUserNameHeader;
|
||||
|
@ -779,8 +785,14 @@ Rectangle {
|
|||
resizable: false;
|
||||
}
|
||||
|
||||
model: ListModel {
|
||||
id: connectionsUserModel;
|
||||
model: connectionsUserModel;
|
||||
Connections {
|
||||
target: connectionsTable.flickableItem;
|
||||
onAtYEndChanged: {
|
||||
if (connectionsTable.flickableItem.atYEnd && !connectionsTable.flickableItem.atYBeginning) {
|
||||
connectionsUserModel.getNextPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This Rectangle refers to each Row in the connectionsTable.
|
||||
|
@ -859,12 +871,9 @@ Rectangle {
|
|||
checked: model && (model.connection === "friend");
|
||||
boxSize: 24;
|
||||
onClicked: {
|
||||
var newValue = model.connection !== "friend";
|
||||
connectionsUserModel.setProperty(model.userIndex, styleData.role, (newValue ? "friend" : "connection"));
|
||||
connectionsUserModelData[model.userIndex][styleData.role] = newValue; // Defensive programming
|
||||
pal.sendToScript({method: newValue ? 'addFriend' : 'removeFriend', params: model.userName});
|
||||
pal.sendToScript({method: checked ? 'addFriend' : 'removeFriend', params: model.userName});
|
||||
|
||||
UserActivityLogger["palAction"](newValue ? styleData.role : "un-" + styleData.role, model.sessionId);
|
||||
UserActivityLogger["palAction"](checked ? styleData.role : "un-" + styleData.role, model.sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1130,16 +1139,6 @@ Rectangle {
|
|||
sortModel();
|
||||
reloadNearby.color = 0;
|
||||
break;
|
||||
case 'connections':
|
||||
var data = message.params;
|
||||
if (pal.debug) {
|
||||
console.log('Got connection data: ', JSON.stringify(data));
|
||||
}
|
||||
connectionsUserModelData = data;
|
||||
sortConnectionsModel();
|
||||
connectionsLoading.visible = false;
|
||||
connectionsRefreshProblemText.visible = false;
|
||||
break;
|
||||
case 'select':
|
||||
var sessionIds = message.params[0];
|
||||
var selected = message.params[1];
|
||||
|
@ -1239,6 +1238,14 @@ Rectangle {
|
|||
reloadNearby.color = 2;
|
||||
}
|
||||
break;
|
||||
case 'inspectionCertificate_resetCert':
|
||||
// marketplaces.js sends out a signal to QML with that method when the tablet screen changes and it's not changed to a commerce-related screen.
|
||||
// We want it to only be handled by the InspectionCertificate.qml, but there's not an easy way of doing that.
|
||||
// As a part of a "cleanup inspectionCertificate_resetCert" ticket, we'll have to figure out less logspammy way of doing what has to be done.
|
||||
break;
|
||||
case 'http.response':
|
||||
http.handleHttpResponse(message);
|
||||
break;
|
||||
default:
|
||||
console.log('Unrecognized message:', JSON.stringify(message));
|
||||
}
|
||||
|
@ -1287,45 +1294,6 @@ Rectangle {
|
|||
nearbyTable.positionViewAtRow(newSelectedIndexes[0], ListView.Beginning);
|
||||
}
|
||||
}
|
||||
function sortConnectionsModel() {
|
||||
var column = connectionsTable.getColumn(connectionsTable.sortIndicatorColumn);
|
||||
var sortProperty = column ? column.role : "userName";
|
||||
var before = (connectionsTable.sortIndicatorOrder === Qt.AscendingOrder) ? -1 : 1;
|
||||
var after = -1 * before;
|
||||
// get selection(s) before sorting
|
||||
var selectedIDs = getSelectedConnectionsUserNames();
|
||||
connectionsUserModelData.sort(function (a, b) {
|
||||
var aValue = a[sortProperty].toString().toLowerCase(), bValue = b[sortProperty].toString().toLowerCase();
|
||||
if (!aValue && !bValue) {
|
||||
return 0;
|
||||
} else if (!aValue) {
|
||||
return after;
|
||||
} else if (!bValue) {
|
||||
return before;
|
||||
}
|
||||
switch (true) {
|
||||
case (aValue < bValue): return before;
|
||||
case (aValue > bValue): return after;
|
||||
default: return 0;
|
||||
}
|
||||
});
|
||||
connectionsTable.selection.clear();
|
||||
|
||||
connectionsUserModel.clear();
|
||||
var userIndex = 0;
|
||||
var newSelectedIndexes = [];
|
||||
connectionsUserModelData.forEach(function (datum) {
|
||||
datum.userIndex = userIndex++;
|
||||
connectionsUserModel.append(datum);
|
||||
if (selectedIDs.indexOf(datum.sessionId) != -1) {
|
||||
newSelectedIndexes.push(datum.userIndex);
|
||||
}
|
||||
});
|
||||
if (newSelectedIndexes.length > 0) {
|
||||
connectionsTable.selection.select(newSelectedIndexes);
|
||||
connectionsTable.positionViewAtRow(newSelectedIndexes[0], ListView.Beginning);
|
||||
}
|
||||
}
|
||||
signal sendToScript(var message);
|
||||
function noticeSelection() {
|
||||
var userIds = [];
|
||||
|
|
39
interface/resources/qml/hifi/RootHttpRequest.qml
Normal file
39
interface/resources/qml/hifi/RootHttpRequest.qml
Normal file
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// RootHttpRequest.qml
|
||||
// qml/hifi
|
||||
//
|
||||
// Create an item of this in the ROOT qml to be able to make http requests.
|
||||
// Used by PSFListModel.qml
|
||||
//
|
||||
// Created by Howard Stearns on 5/29/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
|
||||
Item {
|
||||
property var httpCalls: ({});
|
||||
property var httpCounter: 0;
|
||||
// Public function for initiating an http request.
|
||||
// REQUIRES parent to be root to have sendToScript!
|
||||
function request(options, callback) {
|
||||
console.debug('HttpRequest', JSON.stringify(options));
|
||||
httpCalls[httpCounter] = callback;
|
||||
var message = {method: 'http.request', params: options, id: httpCounter++, jsonrpc: "2.0"};
|
||||
parent.sendToScript(message);
|
||||
}
|
||||
// REQUIRES that parent/root handle http.response message.method in fromScript, by calling this function.
|
||||
function handleHttpResponse(message) {
|
||||
var callback = httpCalls[message.id]; // FIXME: as different top level tablet apps gets loaded, the id repeats. We should drop old app callbacks without warning.
|
||||
if (!callback) {
|
||||
console.warn('No callback for', JSON.stringify(message));
|
||||
return;
|
||||
}
|
||||
delete httpCalls[message.id];
|
||||
console.debug('HttpRequest response', JSON.stringify(message));
|
||||
callback(message.error, message.response);
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ Rectangle {
|
|||
property bool alreadyOwned: false;
|
||||
property int itemPrice: -1;
|
||||
property bool isCertified;
|
||||
property string itemType;
|
||||
property string itemType: "unknown";
|
||||
property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar", "unknown"];
|
||||
property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar", "item"];
|
||||
property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR", "REZ"];
|
||||
|
@ -98,9 +98,6 @@ Rectangle {
|
|||
} else {
|
||||
root.certificateId = result.data.certificate_id;
|
||||
root.itemHref = result.data.download_url;
|
||||
if (result.data.categories.indexOf("Wearables") > -1) {
|
||||
root.itemType = "wearable";
|
||||
}
|
||||
root.activeView = "checkoutSuccess";
|
||||
UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned);
|
||||
}
|
||||
|
@ -170,9 +167,6 @@ Rectangle {
|
|||
root.activeView = "checkoutFailure";
|
||||
} else {
|
||||
root.itemHref = result.data.download_url;
|
||||
if (result.data.categories.indexOf("Wearables") > -1) {
|
||||
root.itemType = "wearable";
|
||||
}
|
||||
root.activeView = "checkoutSuccess";
|
||||
}
|
||||
}
|
||||
|
@ -186,20 +180,6 @@ Rectangle {
|
|||
itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg";
|
||||
}
|
||||
|
||||
onItemHrefChanged: {
|
||||
if (root.itemHref.indexOf(".fst") > -1) {
|
||||
root.itemType = "avatar";
|
||||
} else if (root.itemHref.indexOf('.json.gz') > -1 || root.itemHref.indexOf('.content.zip') > -1) {
|
||||
root.itemType = "contentSet";
|
||||
} else if (root.itemHref.indexOf('.app.json') > -1) {
|
||||
root.itemType = "app";
|
||||
} else if (root.itemHref.indexOf('.json') > -1) {
|
||||
root.itemType = "entity"; // "wearable" type handled later
|
||||
} else {
|
||||
root.itemType = "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
onItemTypeChanged: {
|
||||
if (root.itemType === "entity" || root.itemType === "wearable" ||
|
||||
root.itemType === "contentSet" || root.itemType === "avatar" || root.itemType === "app") {
|
||||
|
@ -1102,6 +1082,7 @@ Rectangle {
|
|||
root.referrer = message.params.referrer;
|
||||
root.itemAuthor = message.params.itemAuthor;
|
||||
root.itemEdition = message.params.itemEdition || -1;
|
||||
root.itemType = message.params.itemType || "unknown";
|
||||
refreshBuyUI();
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -44,7 +44,7 @@ Item {
|
|||
|
||||
Item {
|
||||
id: avatarImage;
|
||||
visible: profileUrl !== "" && userName !== "";
|
||||
visible: profilePicUrl !== "" && userName !== "";
|
||||
// Size
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.left: parent.left;
|
||||
|
|
|
@ -19,6 +19,7 @@ import "../../../../styles-uit"
|
|||
import "../../../../controls-uit" as HifiControlsUit
|
||||
import "../../../../controls" as HifiControls
|
||||
import "../" as HifiCommerceCommon
|
||||
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
|
||||
|
||||
Item {
|
||||
HifiConstants { id: hifi; }
|
||||
|
@ -36,6 +37,8 @@ Item {
|
|||
property string assetName: "";
|
||||
property string assetCertID: "";
|
||||
property string sendingPubliclyEffectImage;
|
||||
property var http;
|
||||
property var listModelName;
|
||||
|
||||
// This object is always used in a popup or full-screen Wallet section.
|
||||
// This MouseArea is used to prevent a user from being
|
||||
|
@ -118,9 +121,7 @@ Item {
|
|||
|
||||
if (root.currentActiveView === 'chooseRecipientConnection') {
|
||||
// Refresh connections model
|
||||
connectionsLoading.visible = false;
|
||||
connectionsLoading.visible = true;
|
||||
sendSignalToParent({method: 'refreshConnections'});
|
||||
connectionsModel.getFirstPage();
|
||||
} else if (root.currentActiveView === 'sendAssetHome') {
|
||||
Commerce.balance();
|
||||
} else if (root.currentActiveView === 'chooseRecipientNearby') {
|
||||
|
@ -392,11 +393,17 @@ Item {
|
|||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
ListModel {
|
||||
HifiModels.PSFListModel {
|
||||
id: connectionsModel;
|
||||
}
|
||||
ListModel {
|
||||
id: filteredConnectionsModel;
|
||||
http: root.http;
|
||||
listModelName: root.listModelName;
|
||||
endpoint: "/api/v1/users?filter=connections";
|
||||
itemsPerPage: 8;
|
||||
listView: connectionsList;
|
||||
processPage: function (data) {
|
||||
return data.users;
|
||||
};
|
||||
searchFilter: filterBar.text;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
@ -472,10 +479,6 @@ Item {
|
|||
anchors.fill: parent;
|
||||
centerPlaceholderGlyph: hifi.glyphs.search;
|
||||
|
||||
onTextChanged: {
|
||||
buildFilteredConnectionsModel();
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
focus = false;
|
||||
}
|
||||
|
@ -495,6 +498,7 @@ Item {
|
|||
|
||||
AnimatedImage {
|
||||
id: connectionsLoading;
|
||||
visible: !connectionsModel.retrievedAtLeastOnePage;
|
||||
source: "../../../../../icons/profilePicLoading.gif"
|
||||
width: 120;
|
||||
height: width;
|
||||
|
@ -515,14 +519,15 @@ Item {
|
|||
}
|
||||
visible: !connectionsLoading.visible;
|
||||
clip: true;
|
||||
model: filteredConnectionsModel;
|
||||
model: connectionsModel;
|
||||
onAtYEndChanged: if (connectionsList.atYEnd && !connectionsList.atYBeginning) { connectionsModel.getNextPage(); }
|
||||
snapMode: ListView.SnapToItem;
|
||||
// Anchors
|
||||
anchors.fill: parent;
|
||||
delegate: ConnectionItem {
|
||||
isSelected: connectionsList.currentIndex === index;
|
||||
userName: model.userName;
|
||||
profilePicUrl: model.profileUrl;
|
||||
userName: model.username;
|
||||
profilePicUrl: model.images.thumbnail;
|
||||
anchors.topMargin: 6;
|
||||
anchors.bottomMargin: 6;
|
||||
|
||||
|
@ -553,7 +558,7 @@ Item {
|
|||
// "Make a Connection" instructions
|
||||
Rectangle {
|
||||
id: connectionInstructions;
|
||||
visible: connectionsModel.count === 0 && !connectionsLoading.visible;
|
||||
visible: connectionsModel.count === 0 && !connectionsModel.searchFilter && !connectionsLoading.visible;
|
||||
anchors.fill: parent;
|
||||
color: "white";
|
||||
|
||||
|
@ -1806,22 +1811,6 @@ Item {
|
|||
// FUNCTION DEFINITIONS START
|
||||
//
|
||||
|
||||
function updateConnections(connections) {
|
||||
connectionsModel.clear();
|
||||
connectionsModel.append(connections);
|
||||
buildFilteredConnectionsModel();
|
||||
connectionsLoading.visible = false;
|
||||
}
|
||||
|
||||
function buildFilteredConnectionsModel() {
|
||||
filteredConnectionsModel.clear();
|
||||
for (var i = 0; i < connectionsModel.count; i++) {
|
||||
if (connectionsModel.get(i).userName.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
|
||||
filteredConnectionsModel.append(connectionsModel.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetSendAssetData() {
|
||||
amountTextField.focus = false;
|
||||
optionalMessage.focus = false;
|
||||
|
|
|
@ -163,7 +163,6 @@ Item {
|
|||
|
||||
Rectangle {
|
||||
id: contextCard;
|
||||
z: 2;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 30;
|
||||
anchors.top: parent.top;
|
||||
|
@ -337,7 +336,6 @@ Item {
|
|||
|
||||
Rectangle {
|
||||
id: permissionExplanationCard;
|
||||
z: 1;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 30;
|
||||
anchors.top: parent.top;
|
||||
|
@ -596,8 +594,8 @@ Item {
|
|||
anchors.fill: parent;
|
||||
hoverEnabled: enabled;
|
||||
onClicked: {
|
||||
contextCard.z = 1;
|
||||
permissionExplanationCard.z = 0;
|
||||
contextCard.visible = true;
|
||||
permissionExplanationCard.visible = false;
|
||||
root.sendToPurchases({ method: 'flipCard' });
|
||||
}
|
||||
onEntered: {
|
||||
|
@ -779,8 +777,8 @@ Item {
|
|||
noPermissionGlyph.color = hifi.colors.redAccent;
|
||||
}
|
||||
onClicked: {
|
||||
contextCard.z = 0;
|
||||
permissionExplanationCard.z = 1;
|
||||
contextCard.visible = false;
|
||||
permissionExplanationCard.visible = true;
|
||||
root.sendToPurchases({ method: 'flipCard' });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,12 @@ import QtQuick 2.5
|
|||
import "../../../styles-uit"
|
||||
import "../../../controls-uit" as HifiControlsUit
|
||||
import "../../../controls" as HifiControls
|
||||
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
|
||||
import "../wallet" as HifiWallet
|
||||
import "../common" as HifiCommerceCommon
|
||||
import "../inspectionCertificate" as HifiInspectionCertificate
|
||||
import "../common/sendAsset" as HifiSendAsset
|
||||
import "../.." as HifiCommon
|
||||
|
||||
// references XXX from root context
|
||||
|
||||
|
@ -34,12 +36,17 @@ Rectangle {
|
|||
property bool punctuationMode: false;
|
||||
property bool isShowingMyItems: false;
|
||||
property bool isDebuggingFirstUseTutorial: false;
|
||||
property int pendingItemCount: 0;
|
||||
property string installedApps;
|
||||
property bool keyboardRaised: false;
|
||||
property int numUpdatesAvailable: 0;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
function getPurchases() {
|
||||
root.activeView = "purchasesMain";
|
||||
root.installedApps = Commerce.getInstalledApps();
|
||||
purchasesModel.getFirstPage();
|
||||
Commerce.getAvailableUpdates();
|
||||
}
|
||||
Connections {
|
||||
target: Commerce;
|
||||
|
||||
|
@ -62,10 +69,7 @@ Rectangle {
|
|||
if ((Settings.getValue("isFirstUseOfPurchases", true) || root.isDebuggingFirstUseTutorial) && root.activeView !== "firstUseTutorial") {
|
||||
root.activeView = "firstUseTutorial";
|
||||
} else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") {
|
||||
root.activeView = "purchasesMain";
|
||||
root.installedApps = Commerce.getInstalledApps();
|
||||
Commerce.inventory();
|
||||
Commerce.getAvailableUpdates();
|
||||
getPurchases();
|
||||
}
|
||||
} else {
|
||||
console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus);
|
||||
|
@ -81,39 +85,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
onInventoryResult: {
|
||||
purchasesReceived = true;
|
||||
|
||||
if (result.status !== 'success') {
|
||||
console.log("Failed to get purchases", result.message);
|
||||
} else if (!purchasesContentsList.dragging) { // Don't modify the view if the user's scrolling
|
||||
var inventoryResult = processInventoryResult(result.data.assets);
|
||||
|
||||
var currentIndex = purchasesContentsList.currentIndex === -1 ? 0 : purchasesContentsList.currentIndex;
|
||||
purchasesModel.clear();
|
||||
purchasesModel.append(inventoryResult);
|
||||
|
||||
root.pendingItemCount = 0;
|
||||
for (var i = 0; i < purchasesModel.count; i++) {
|
||||
if (purchasesModel.get(i).status === "pending") {
|
||||
root.pendingItemCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (previousPurchasesModel.count !== 0) {
|
||||
checkIfAnyItemStatusChanged();
|
||||
} else {
|
||||
// Fill statusChanged default value
|
||||
// Not doing this results in the default being true...
|
||||
for (var i = 0; i < purchasesModel.count; i++) {
|
||||
purchasesModel.setProperty(i, "statusChanged", false);
|
||||
}
|
||||
}
|
||||
previousPurchasesModel.append(inventoryResult);
|
||||
|
||||
buildFilteredPurchasesModel();
|
||||
|
||||
purchasesContentsList.positionViewAtIndex(currentIndex, ListView.Beginning);
|
||||
}
|
||||
purchasesModel.handlePage(result.status !== "success" && result.message, result);
|
||||
}
|
||||
|
||||
onAvailableUpdatesResult: {
|
||||
|
@ -134,6 +106,10 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
onIsShowingMyItemsChanged: {
|
||||
getPurchases();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: notSetUpTimer;
|
||||
interval: 200;
|
||||
|
@ -172,8 +148,14 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
HifiCommon.RootHttpRequest {
|
||||
id: http;
|
||||
}
|
||||
|
||||
HifiSendAsset.SendAsset {
|
||||
id: sendAsset;
|
||||
http: http;
|
||||
listModelName: "Gift Connections";
|
||||
z: 998;
|
||||
visible: root.activeView === "giftAsset";
|
||||
anchors.fill: parent;
|
||||
|
@ -183,9 +165,7 @@ Rectangle {
|
|||
Connections {
|
||||
onSendSignalToParent: {
|
||||
if (msg.method === 'sendAssetHome_back' || msg.method === 'closeSendAsset') {
|
||||
root.activeView = "purchasesMain";
|
||||
Commerce.inventory();
|
||||
Commerce.getAvailableUpdates();
|
||||
getPurchases();
|
||||
} else {
|
||||
sendToScript(msg);
|
||||
}
|
||||
|
@ -449,10 +429,7 @@ Rectangle {
|
|||
case 'tutorial_skipClicked':
|
||||
case 'tutorial_finished':
|
||||
Settings.setValue("isFirstUseOfPurchases", false);
|
||||
root.activeView = "purchasesMain";
|
||||
root.installedApps = Commerce.getInstalledApps();
|
||||
Commerce.inventory();
|
||||
Commerce.getAvailableUpdates();
|
||||
getPurchases();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -528,7 +505,7 @@ Rectangle {
|
|||
},
|
||||
{
|
||||
"displayName": "Content Set",
|
||||
"filterName": "contentSet"
|
||||
"filterName": "content_set"
|
||||
},
|
||||
{
|
||||
"displayName": "Entity",
|
||||
|
@ -540,7 +517,7 @@ Rectangle {
|
|||
},
|
||||
{
|
||||
"displayName": "Updatable",
|
||||
"filterName": "updatable"
|
||||
"filterName": "updated"
|
||||
}
|
||||
]
|
||||
filterBar.primaryFilterChoices.clear();
|
||||
|
@ -548,14 +525,12 @@ Rectangle {
|
|||
}
|
||||
|
||||
onPrimaryFilter_displayNameChanged: {
|
||||
buildFilteredPurchasesModel();
|
||||
purchasesContentsList.positionViewAtIndex(0, ListView.Beginning)
|
||||
purchasesModel.tagsFilter = filterBar.primaryFilter_filterName;
|
||||
filterBar.previousPrimaryFilter = filterBar.primaryFilter_displayName;
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
buildFilteredPurchasesModel();
|
||||
purchasesContentsList.positionViewAtIndex(0, ListView.Beginning)
|
||||
purchasesModel.searchFilter = filterBar.text;
|
||||
filterBar.previousText = filterBar.text;
|
||||
}
|
||||
}
|
||||
|
@ -574,24 +549,41 @@ Rectangle {
|
|||
anchors.topMargin: 16;
|
||||
}
|
||||
|
||||
ListModel {
|
||||
HifiModels.PSFListModel {
|
||||
id: purchasesModel;
|
||||
}
|
||||
ListModel {
|
||||
id: previousPurchasesModel;
|
||||
}
|
||||
HifiCommerceCommon.SortableListModel {
|
||||
id: tempPurchasesModel;
|
||||
}
|
||||
HifiCommerceCommon.SortableListModel {
|
||||
id: filteredPurchasesModel;
|
||||
itemsPerPage: 6;
|
||||
listModelName: 'purchases';
|
||||
getPage: function () {
|
||||
console.debug('getPage', purchasesModel.listModelName, root.isShowingMyItems, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage);
|
||||
Commerce.inventory(
|
||||
root.isShowingMyItems ? "proofs" : "purchased",
|
||||
filterBar.primaryFilter_filterName,
|
||||
filterBar.text,
|
||||
purchasesModel.currentPageToRetrieve,
|
||||
purchasesModel.itemsPerPage
|
||||
);
|
||||
}
|
||||
processPage: function(data) {
|
||||
purchasesReceived = true; // HRS FIXME?
|
||||
data.assets.forEach(function (item) {
|
||||
if (item.status.length > 1) { console.warn("Unrecognized inventory status", item); }
|
||||
item.status = item.status[0];
|
||||
item.categories = item.categories.join(';');
|
||||
item.cardBackVisible = false;
|
||||
item.isInstalled = root.installedApps.indexOf(item.id) > -1;
|
||||
item.wornEntityID = '';
|
||||
});
|
||||
sendToScript({ method: 'purchases_updateWearables' });
|
||||
|
||||
return data.assets;
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: purchasesContentsList;
|
||||
visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0);
|
||||
visible: purchasesModel.count !== 0;
|
||||
clip: true;
|
||||
model: filteredPurchasesModel;
|
||||
model: purchasesModel;
|
||||
snapMode: ListView.SnapToItem;
|
||||
// Anchors
|
||||
anchors.top: separator.bottom;
|
||||
|
@ -608,13 +600,13 @@ Rectangle {
|
|||
itemEdition: model.edition_number;
|
||||
numberSold: model.number_sold;
|
||||
limitedRun: model.limited_run;
|
||||
displayedItemCount: model.displayedItemCount;
|
||||
cardBackVisible: model.cardBackVisible;
|
||||
isInstalled: model.isInstalled;
|
||||
displayedItemCount: 999; // For now (and maybe longer), we're going to display all the edition numbers.
|
||||
cardBackVisible: model.cardBackVisible || false;
|
||||
isInstalled: model.isInstalled || false;
|
||||
wornEntityID: model.wornEntityID;
|
||||
upgradeUrl: model.upgrade_url;
|
||||
upgradeTitle: model.upgrade_title;
|
||||
itemType: model.itemType;
|
||||
itemType: model.item_type;
|
||||
isShowingMyItems: root.isShowingMyItems;
|
||||
valid: model.valid;
|
||||
anchors.topMargin: 10;
|
||||
|
@ -706,11 +698,11 @@ Rectangle {
|
|||
} else if (msg.method === "setFilterText") {
|
||||
filterBar.text = msg.filterText;
|
||||
} else if (msg.method === "flipCard") {
|
||||
for (var i = 0; i < filteredPurchasesModel.count; i++) {
|
||||
for (var i = 0; i < purchasesModel.count; i++) {
|
||||
if (i !== index || msg.closeAll) {
|
||||
filteredPurchasesModel.setProperty(i, "cardBackVisible", false);
|
||||
purchasesModel.setProperty(i, "cardBackVisible", false);
|
||||
} else {
|
||||
filteredPurchasesModel.setProperty(i, "cardBackVisible", true);
|
||||
purchasesModel.setProperty(i, "cardBackVisible", true);
|
||||
}
|
||||
}
|
||||
} else if (msg.method === "updateItemClicked") {
|
||||
|
@ -761,7 +753,7 @@ Rectangle {
|
|||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = function() {
|
||||
Entities.deleteEntity(msg.wornEntityID);
|
||||
filteredPurchasesModel.setProperty(index, 'wornEntityID', '');
|
||||
purchasesModel.setProperty(index, 'wornEntityID', '');
|
||||
root.activeView = "giftAsset";
|
||||
lightboxPopup.visible = false;
|
||||
};
|
||||
|
@ -773,6 +765,14 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onAtYEndChanged: {
|
||||
if (purchasesContentsList.atYEnd && !purchasesContentsList.atYBeginning) {
|
||||
console.log("User scrolled to the bottom of 'Purchases'.");
|
||||
purchasesModel.getNextPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
@ -953,146 +953,14 @@ Rectangle {
|
|||
//
|
||||
// FUNCTION DEFINITIONS START
|
||||
//
|
||||
|
||||
function processInventoryResult(inventory) {
|
||||
for (var i = 0; i < inventory.length; i++) {
|
||||
if (inventory[i].status.length > 1) {
|
||||
console.log("WARNING: Inventory result index " + i + " has a status of length >1!")
|
||||
}
|
||||
inventory[i].status = inventory[i].status[0];
|
||||
inventory[i].categories = inventory[i].categories.join(';');
|
||||
}
|
||||
return inventory;
|
||||
}
|
||||
|
||||
function populateDisplayedItemCounts() {
|
||||
var itemCountDictionary = {};
|
||||
var currentItemId;
|
||||
for (var i = 0; i < filteredPurchasesModel.count; i++) {
|
||||
currentItemId = filteredPurchasesModel.get(i).id;
|
||||
if (itemCountDictionary[currentItemId] === undefined) {
|
||||
itemCountDictionary[currentItemId] = 1;
|
||||
} else {
|
||||
itemCountDictionary[currentItemId]++;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < filteredPurchasesModel.count; i++) {
|
||||
filteredPurchasesModel.setProperty(i, "displayedItemCount", itemCountDictionary[filteredPurchasesModel.get(i).id]);
|
||||
}
|
||||
}
|
||||
|
||||
function sortByDate() {
|
||||
filteredPurchasesModel.sortColumnName = "purchase_date";
|
||||
filteredPurchasesModel.isSortingDescending = true;
|
||||
filteredPurchasesModel.valuesAreNumerical = true;
|
||||
filteredPurchasesModel.quickSort();
|
||||
}
|
||||
|
||||
function buildFilteredPurchasesModel() {
|
||||
var sameItemCount = 0;
|
||||
|
||||
tempPurchasesModel.clear();
|
||||
|
||||
for (var i = 0; i < purchasesModel.count; i++) {
|
||||
if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
|
||||
if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) {
|
||||
tempPurchasesModel.insert(0, purchasesModel.get(i));
|
||||
} else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") ||
|
||||
(!root.isShowingMyItems && purchasesModel.get(i).edition_number !== "0")) {
|
||||
tempPurchasesModel.append(purchasesModel.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// primaryFilter filtering and adding of itemType property to model
|
||||
var currentItemType, currentRootFileUrl, currentCategories;
|
||||
for (var i = 0; i < tempPurchasesModel.count; i++) {
|
||||
currentRootFileUrl = tempPurchasesModel.get(i).root_file_url;
|
||||
currentCategories = tempPurchasesModel.get(i).categories;
|
||||
|
||||
if (currentRootFileUrl.indexOf(".fst") > -1) {
|
||||
currentItemType = "avatar";
|
||||
} else if (currentCategories.indexOf("Wearables") > -1) {
|
||||
currentItemType = "wearable";
|
||||
} else if (currentRootFileUrl.endsWith('.json.gz') || currentRootFileUrl.endsWith('.content.zip')) {
|
||||
currentItemType = "contentSet";
|
||||
} else if (currentRootFileUrl.endsWith('.app.json')) {
|
||||
currentItemType = "app";
|
||||
} else if (currentRootFileUrl.endsWith('.json')) {
|
||||
currentItemType = "entity";
|
||||
} else {
|
||||
currentItemType = "unknown";
|
||||
}
|
||||
if (filterBar.primaryFilter_displayName !== "" &&
|
||||
((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgrade_url === "") ||
|
||||
(filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_filterName.toLowerCase() !== currentItemType.toLowerCase()))) {
|
||||
tempPurchasesModel.remove(i);
|
||||
i--;
|
||||
} else {
|
||||
tempPurchasesModel.setProperty(i, 'itemType', currentItemType);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < tempPurchasesModel.count; i++) {
|
||||
if (!filteredPurchasesModel.get(i)) {
|
||||
sameItemCount = -1;
|
||||
break;
|
||||
} else if (tempPurchasesModel.get(i).itemId === filteredPurchasesModel.get(i).itemId &&
|
||||
tempPurchasesModel.get(i).edition_number === filteredPurchasesModel.get(i).edition_number &&
|
||||
tempPurchasesModel.get(i).status === filteredPurchasesModel.get(i).status) {
|
||||
sameItemCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (sameItemCount !== tempPurchasesModel.count ||
|
||||
filterBar.text !== filterBar.previousText ||
|
||||
filterBar.primaryFilter !== filterBar.previousPrimaryFilter) {
|
||||
filteredPurchasesModel.clear();
|
||||
var currentId;
|
||||
for (var i = 0; i < tempPurchasesModel.count; i++) {
|
||||
currentId = tempPurchasesModel.get(i).id;
|
||||
filteredPurchasesModel.append(tempPurchasesModel.get(i));
|
||||
filteredPurchasesModel.setProperty(i, 'cardBackVisible', false);
|
||||
filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1));
|
||||
filteredPurchasesModel.setProperty(i, 'wornEntityID', '');
|
||||
}
|
||||
|
||||
sendToScript({ method: 'purchases_updateWearables' });
|
||||
populateDisplayedItemCounts();
|
||||
sortByDate();
|
||||
}
|
||||
}
|
||||
|
||||
function checkIfAnyItemStatusChanged() {
|
||||
var currentPurchasesModelId, currentPurchasesModelEdition, currentPurchasesModelStatus;
|
||||
var previousPurchasesModelStatus;
|
||||
for (var i = 0; i < purchasesModel.count; i++) {
|
||||
currentPurchasesModelId = purchasesModel.get(i).id;
|
||||
currentPurchasesModelEdition = purchasesModel.get(i).edition_number;
|
||||
currentPurchasesModelStatus = purchasesModel.get(i).status;
|
||||
|
||||
for (var j = 0; j < previousPurchasesModel.count; j++) {
|
||||
previousPurchasesModelStatus = previousPurchasesModel.get(j).status;
|
||||
if (currentPurchasesModelId === previousPurchasesModel.get(j).id &&
|
||||
currentPurchasesModelEdition === previousPurchasesModel.get(j).edition_number &&
|
||||
currentPurchasesModelStatus !== previousPurchasesModelStatus) {
|
||||
|
||||
purchasesModel.setProperty(i, "statusChanged", true);
|
||||
} else {
|
||||
purchasesModel.setProperty(i, "statusChanged", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateCurrentlyWornWearables(wearables) {
|
||||
for (var i = 0; i < filteredPurchasesModel.count; i++) {
|
||||
for (var i = 0; i < purchasesModel.count; i++) {
|
||||
for (var j = 0; j < wearables.length; j++) {
|
||||
if (filteredPurchasesModel.get(i).itemType === "wearable" &&
|
||||
wearables[j].entityCertID === filteredPurchasesModel.get(i).certificate_id &&
|
||||
wearables[j].entityEdition.toString() === filteredPurchasesModel.get(i).edition_number) {
|
||||
filteredPurchasesModel.setProperty(i, 'wornEntityID', wearables[j].entityID);
|
||||
if (purchasesModel.get(i).itemType === "wearable" &&
|
||||
wearables[j].entityCertID === purchasesModel.get(i).certificate_id &&
|
||||
wearables[j].entityEdition.toString() === purchasesModel.get(i).edition_number) {
|
||||
purchasesModel.setProperty(i, 'wornEntityID', wearables[j].entityID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1149,7 +1017,7 @@ Rectangle {
|
|||
switch (message.method) {
|
||||
case 'updatePurchases':
|
||||
referrerURL = message.referrerURL || "";
|
||||
titleBarContainer.referrerURL = message.referrerURL;
|
||||
titleBarContainer.referrerURL = message.referrerURL || "";
|
||||
filterBar.text = message.filterText ? message.filterText : "";
|
||||
break;
|
||||
case 'inspectionCertificate_setCertificateId':
|
||||
|
@ -1168,6 +1036,9 @@ Rectangle {
|
|||
case 'updateWearables':
|
||||
updateCurrentlyWornWearables(message.wornWearables);
|
||||
break;
|
||||
case 'http.response':
|
||||
http.handleHttpResponse(message);
|
||||
break;
|
||||
default:
|
||||
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import "../../../controls-uit" as HifiControlsUit
|
|||
import "../../../controls" as HifiControls
|
||||
import "../common" as HifiCommerceCommon
|
||||
import "../common/sendAsset"
|
||||
import "../.." as HifiCommon
|
||||
|
||||
Rectangle {
|
||||
HifiConstants { id: hifi; }
|
||||
|
@ -343,8 +344,14 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
HifiCommon.RootHttpRequest {
|
||||
id: http;
|
||||
}
|
||||
|
||||
SendAsset {
|
||||
id: sendMoney;
|
||||
http: http;
|
||||
listModelName: "Send Money Connections";
|
||||
z: 997;
|
||||
visible: root.activeView === "sendMoney";
|
||||
anchors.fill: parent;
|
||||
|
@ -768,6 +775,13 @@ Rectangle {
|
|||
case 'updateSelectedRecipientUsername':
|
||||
sendMoney.fromScript(message);
|
||||
break;
|
||||
case 'http.response':
|
||||
http.handleHttpResponse(message);
|
||||
break;
|
||||
case 'palIsStale':
|
||||
case 'avatarDisconnected':
|
||||
// Because we don't have "channels" for sending messages to a specific QML object, the messages are broadcast to all QML Items. If an Item of yours happens to be visible when some script sends a message with a method you don't expect, you'll get "Unrecognized message..." logs.
|
||||
break;
|
||||
default:
|
||||
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
|
||||
}
|
||||
|
|
|
@ -18,27 +18,17 @@ import QtQuick.Controls 2.2
|
|||
import "../../../styles-uit"
|
||||
import "../../../controls-uit" as HifiControlsUit
|
||||
import "../../../controls" as HifiControls
|
||||
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
|
||||
|
||||
Item {
|
||||
HifiConstants { id: hifi; }
|
||||
|
||||
id: root;
|
||||
property bool initialHistoryReceived: false;
|
||||
property bool historyRequestPending: true;
|
||||
property bool noMoreHistoryData: false;
|
||||
property int pendingCount: 0;
|
||||
property int currentHistoryPage: 1;
|
||||
property var pagesAlreadyAdded: new Array();
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
transactionHistoryModel.clear();
|
||||
Commerce.balance();
|
||||
initialHistoryReceived = false;
|
||||
root.currentHistoryPage = 1;
|
||||
root.noMoreHistoryData = false;
|
||||
root.historyRequestPending = true;
|
||||
Commerce.history(root.currentHistoryPage);
|
||||
transactionHistoryModel.getFirstPage();
|
||||
Commerce.getAvailableUpdates();
|
||||
} else {
|
||||
refreshTimer.stop();
|
||||
|
@ -53,86 +43,7 @@ Item {
|
|||
}
|
||||
|
||||
onHistoryResult : {
|
||||
root.initialHistoryReceived = true;
|
||||
root.historyRequestPending = false;
|
||||
|
||||
if (result.status === 'success') {
|
||||
var currentPage = parseInt(result.current_page);
|
||||
|
||||
if (result.data.history.length === 0) {
|
||||
root.noMoreHistoryData = true;
|
||||
console.log("No more data to retrieve from Commerce.history() endpoint.")
|
||||
} else if (root.currentHistoryPage === 1) {
|
||||
var sameItemCount = 0;
|
||||
tempTransactionHistoryModel.clear();
|
||||
|
||||
tempTransactionHistoryModel.append(result.data.history);
|
||||
|
||||
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
|
||||
if (!transactionHistoryModel.get(i)) {
|
||||
sameItemCount = -1;
|
||||
break;
|
||||
} else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type &&
|
||||
tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) {
|
||||
sameItemCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (sameItemCount !== tempTransactionHistoryModel.count) {
|
||||
transactionHistoryModel.clear();
|
||||
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
|
||||
transactionHistoryModel.append(tempTransactionHistoryModel.get(i));
|
||||
}
|
||||
calculatePendingAndInvalidated();
|
||||
}
|
||||
} else {
|
||||
if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) {
|
||||
console.log("Page " + currentPage + " of history has already been added to the list.");
|
||||
} else {
|
||||
// First, add the history result to a temporary model
|
||||
tempTransactionHistoryModel.clear();
|
||||
tempTransactionHistoryModel.append(result.data.history);
|
||||
|
||||
// Make a note that we've already added this page to the model...
|
||||
root.pagesAlreadyAdded.push(currentPage);
|
||||
|
||||
var insertionIndex = 0;
|
||||
// If there's nothing in the model right now, we don't need to modify insertionIndex.
|
||||
if (transactionHistoryModel.count !== 0) {
|
||||
var currentIteratorPage;
|
||||
// Search through the whole transactionHistoryModel and look for the insertion point.
|
||||
// The insertion point is found when the result page from the server is less than
|
||||
// the page that the current item came from, OR when we've reached the end of the whole model.
|
||||
for (var i = 0; i < transactionHistoryModel.count; i++) {
|
||||
currentIteratorPage = transactionHistoryModel.get(i).resultIsFromPage;
|
||||
|
||||
if (currentPage < currentIteratorPage) {
|
||||
insertionIndex = i;
|
||||
break;
|
||||
} else if (i === transactionHistoryModel.count - 1) {
|
||||
insertionIndex = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go through the results we just got back from the server, setting the "resultIsFromPage"
|
||||
// property of those results and adding them to the main model.
|
||||
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
|
||||
tempTransactionHistoryModel.setProperty(i, "resultIsFromPage", currentPage);
|
||||
transactionHistoryModel.insert(i + insertionIndex, tempTransactionHistoryModel.get(i))
|
||||
}
|
||||
|
||||
calculatePendingAndInvalidated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only auto-refresh if the user hasn't scrolled
|
||||
// and there is more data to grab
|
||||
if (transactionHistory.atYBeginning && !root.noMoreHistoryData) {
|
||||
refreshTimer.start();
|
||||
}
|
||||
transactionHistoryModel.handlePage(null, result);
|
||||
}
|
||||
|
||||
onAvailableUpdatesResult: {
|
||||
|
@ -147,7 +58,7 @@ Item {
|
|||
Connections {
|
||||
target: GlobalServices
|
||||
onMyUsernameChanged: {
|
||||
transactionHistoryModel.clear();
|
||||
transactionHistoryModel.resetModel();
|
||||
usernameText.text = Account.username;
|
||||
}
|
||||
}
|
||||
|
@ -235,9 +146,8 @@ Item {
|
|||
onTriggered: {
|
||||
if (transactionHistory.atYBeginning) {
|
||||
console.log("Refreshing 1st Page of Recent Activity...");
|
||||
root.historyRequestPending = true;
|
||||
Commerce.balance();
|
||||
Commerce.history(1);
|
||||
transactionHistoryModel.getFirstPage("delayedClear");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -299,11 +209,42 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: tempTransactionHistoryModel;
|
||||
}
|
||||
ListModel {
|
||||
HifiModels.PSFListModel {
|
||||
id: transactionHistoryModel;
|
||||
listModelName: "transaction history"; // For debugging. Alternatively, we could specify endpoint for that purpose, even though it's not used directly.
|
||||
itemsPerPage: 6;
|
||||
getPage: function () {
|
||||
console.debug('getPage', transactionHistoryModel.listModelName, transactionHistoryModel.currentPageToRetrieve);
|
||||
Commerce.history(transactionHistoryModel.currentPageToRetrieve, transactionHistoryModel.itemsPerPage);
|
||||
}
|
||||
processPage: function (data) {
|
||||
console.debug('processPage', transactionHistoryModel.listModelName, JSON.stringify(data));
|
||||
var result, pending; // Set up or get the accumulator for pending.
|
||||
if (transactionHistoryModel.currentPageToRetrieve == 1) {
|
||||
pending = {transaction_type: "pendingCount", count: 0};
|
||||
result = [pending];
|
||||
} else {
|
||||
pending = transactionHistoryModel.get(0);
|
||||
result = [];
|
||||
}
|
||||
|
||||
// Either add to pending, or to result.
|
||||
// Note that you only see a page of pending stuff until you scroll...
|
||||
data.history.forEach(function (item) {
|
||||
if (item.status === 'pending') {
|
||||
pending.count++;
|
||||
} else {
|
||||
result = result.concat(item);
|
||||
}
|
||||
});
|
||||
|
||||
// Only auto-refresh if the user hasn't scrolled
|
||||
// and there is more data to grab
|
||||
if (transactionHistory.atYBeginning && data.history.length) {
|
||||
refreshTimer.start();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Item {
|
||||
anchors.top: recentActivityText.bottom;
|
||||
|
@ -312,8 +253,8 @@ Item {
|
|||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
|
||||
Item {
|
||||
visible: transactionHistoryModel.count === 0 && root.initialHistoryReceived;
|
||||
Item { // On empty history. We don't want to flash and then replace, so don't show until we know we're zero.
|
||||
visible: transactionHistoryModel.count === 0 && transactionHistoryModel.currentPageToRetrieve < 0;
|
||||
anchors.centerIn: parent;
|
||||
width: parent.width - 12;
|
||||
height: parent.height;
|
||||
|
@ -385,10 +326,10 @@ Item {
|
|||
model: transactionHistoryModel;
|
||||
delegate: Item {
|
||||
width: parent.width;
|
||||
height: (model.transaction_type === "pendingCount" && root.pendingCount !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0);
|
||||
height: (model.transaction_type === "pendingCount" && model.count !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0);
|
||||
|
||||
Item {
|
||||
visible: model.transaction_type === "pendingCount" && root.pendingCount !== 0;
|
||||
visible: model.transaction_type === "pendingCount" && model.count !== 0;
|
||||
anchors.top: parent.top;
|
||||
anchors.left: parent.left;
|
||||
width: parent.width;
|
||||
|
@ -397,7 +338,7 @@ Item {
|
|||
AnonymousProRegular {
|
||||
id: pendingCountText;
|
||||
anchors.fill: parent;
|
||||
text: root.pendingCount + ' Transaction' + (root.pendingCount > 1 ? 's' : '') + ' Pending';
|
||||
text: model.count + ' Transaction' + (model.count > 1 ? 's' : '') + ' Pending';
|
||||
size: 18;
|
||||
color: hifi.colors.blueAccent;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
|
@ -460,14 +401,9 @@ Item {
|
|||
}
|
||||
}
|
||||
onAtYEndChanged: {
|
||||
if (transactionHistory.atYEnd) {
|
||||
if (transactionHistory.atYEnd && !transactionHistory.atYBeginning) {
|
||||
console.log("User scrolled to the bottom of 'Recent Activity'.");
|
||||
if (!root.historyRequestPending && !root.noMoreHistoryData) {
|
||||
// Grab next page of results and append to model
|
||||
root.historyRequestPending = true;
|
||||
Commerce.history(++root.currentHistoryPage);
|
||||
console.log("Fetching Page " + root.currentHistoryPage + " of Recent Activity...");
|
||||
}
|
||||
transactionHistoryModel.getNextPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -506,40 +442,6 @@ Item {
|
|||
return year + '-' + month + '-' + day + '<br>' + drawnHour + ':' + min + amOrPm;
|
||||
}
|
||||
|
||||
|
||||
function calculatePendingAndInvalidated(startingPendingCount) {
|
||||
var pendingCount = startingPendingCount ? startingPendingCount : 0;
|
||||
for (var i = 0; i < transactionHistoryModel.count; i++) {
|
||||
if (transactionHistoryModel.get(i).status === "pending") {
|
||||
pendingCount++;
|
||||
}
|
||||
}
|
||||
|
||||
root.pendingCount = pendingCount;
|
||||
if (pendingCount > 0) {
|
||||
transactionHistoryModel.insert(0, {"transaction_type": "pendingCount"});
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Function Name: fromScript()
|
||||
//
|
||||
// Relevant Variables:
|
||||
// None
|
||||
//
|
||||
// Arguments:
|
||||
// message: The message sent from the JavaScript.
|
||||
// Messages are in format "{method, params}", like json-rpc.
|
||||
//
|
||||
// Description:
|
||||
// Called when a message is received from a script.
|
||||
//
|
||||
function fromScript(message) {
|
||||
switch (message.method) {
|
||||
default:
|
||||
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
signal sendSignalToWallet(var msg);
|
||||
|
||||
//
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
168
interface/resources/qml/hifi/models/PSFListModel.qml
Normal file
168
interface/resources/qml/hifi/models/PSFListModel.qml
Normal file
|
@ -0,0 +1,168 @@
|
|||
//
|
||||
// PSFListModel.qml
|
||||
// qml/hifi/commerce/common
|
||||
//
|
||||
// PSFListModel
|
||||
// "PSF" stands for:
|
||||
// - Paged
|
||||
// - Sortable
|
||||
// - Filterable
|
||||
//
|
||||
// Created by Zach Fox on 2018-05-15
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.7
|
||||
|
||||
ListModel {
|
||||
id: root;
|
||||
// Used when printing debug statements
|
||||
property string listModelName: endpoint;
|
||||
|
||||
// Parameters. Even if you override getPage, below, please set these for clarity and consistency, when applicable.
|
||||
// E.g., your getPage function could refer to this sortKey, etc.
|
||||
property string endpoint;
|
||||
property string sortProperty; // Currently only handles sorting on one column, which fits with current needs and tables.
|
||||
property bool sortAscending;
|
||||
property string sortKey: !sortProperty ? '' : (sortProperty + "," + (sortAscending ? "ASC" : "DESC"));
|
||||
property string searchFilter: "";
|
||||
property string tagsFilter;
|
||||
|
||||
// QML fires the following changed handlers even when first instantiating the Item. So we need a guard against firing them too early.
|
||||
property bool initialized: false;
|
||||
Component.onCompleted: initialized = true;
|
||||
onEndpointChanged: if (initialized) { getFirstPage('delayClear'); }
|
||||
onSortKeyChanged: if (initialized) { getFirstPage('delayClear'); }
|
||||
onSearchFilterChanged: if (initialized) { getFirstPage('delayClear'); }
|
||||
onTagsFilterChanged: if (initialized) { getFirstPage('delayClear'); }
|
||||
|
||||
property int itemsPerPage: 100;
|
||||
|
||||
// State.
|
||||
property int currentPageToRetrieve: 0; // 0 = before first page. -1 = we have them all. Otherwise 1-based page number.
|
||||
property bool retrievedAtLeastOnePage: false;
|
||||
// We normally clear on reset. But if we want to "refresh", we can delay clearing the model until we get a result.
|
||||
// Not normally set directly, but rather by giving a truthy argument to getFirstPage(true);
|
||||
property bool delayedClear: false;
|
||||
function resetModel() {
|
||||
if (!delayedClear) { root.clear(); }
|
||||
currentPageToRetrieve = 1;
|
||||
retrievedAtLeastOnePage = false;
|
||||
}
|
||||
|
||||
// Page processing.
|
||||
|
||||
// Override to return one property of data, and/or to transform the elements. Must return an array of model elements.
|
||||
property var processPage: function (data) { return data; }
|
||||
|
||||
property var listView; // Optional. For debugging.
|
||||
// Check consistency and call processPage.
|
||||
function handlePage(error, response) {
|
||||
var processed;
|
||||
console.debug('handlePage', listModelName, additionalFirstPageRequested, error, JSON.stringify(response));
|
||||
function fail(message) {
|
||||
console.warn("Warning page fail", listModelName, JSON.stringify(message));
|
||||
currentPageToRetrieve = -1;
|
||||
requestPending = false;
|
||||
delayedClear = false;
|
||||
}
|
||||
if (error || (response.status !== 'success')) {
|
||||
return fail(error || response.status);
|
||||
}
|
||||
if (!requestPending) {
|
||||
return fail("No request in flight.");
|
||||
}
|
||||
requestPending = false;
|
||||
if (response.current_page && response.current_page !== currentPageToRetrieve) { // Not all endpoints specify this property.
|
||||
return fail("Mismatched page, expected:" + currentPageToRetrieve);
|
||||
}
|
||||
processed = processPage(response.data || response);
|
||||
if (response.total_pages && (response.total_pages === currentPageToRetrieve)) {
|
||||
currentPageToRetrieve = -1;
|
||||
}
|
||||
|
||||
if (delayedClear) {
|
||||
root.clear();
|
||||
delayedClear = false;
|
||||
}
|
||||
root.append(processed); // FIXME keep index steady, and apply any post sort
|
||||
retrievedAtLeastOnePage = true;
|
||||
// Suppose two properties change at once, and both of their change handlers request a new first page.
|
||||
// (An example is when the a filter box gets cleared with text in it, so that the search and tags are both reset.)
|
||||
// Or suppose someone just types new search text quicker than the server response.
|
||||
// In these cases, we would have multiple requests in flight, and signal based responses aren't generally very good
|
||||
// at matching up the right handler with the right message. Rather than require all the APIs to carefully handle such,
|
||||
// and also to cut down on useless requests, we take care of that case here.
|
||||
if (additionalFirstPageRequested) {
|
||||
console.debug('deferred getFirstPage', listModelName);
|
||||
additionalFirstPageRequested = false;
|
||||
getFirstPage('delayedClear');
|
||||
}
|
||||
}
|
||||
function debugView(label) {
|
||||
if (!listView) { return; }
|
||||
console.debug(label, listModelName, 'perPage:', itemsPerPage, 'count:', listView.count,
|
||||
'index:', listView.currentIndex, 'section:', listView.currentSection,
|
||||
'atYBeginning:', listView.atYBeginning, 'atYEnd:', listView.atYEnd,
|
||||
'y:', listView.y, 'contentY:', listView.contentY);
|
||||
}
|
||||
|
||||
// Override either http or getPage.
|
||||
property var http; // An Item that has a request function.
|
||||
property var getPage: function () { // Any override MUST call handlePage(), above, even if results empty.
|
||||
if (!http) { return console.warn("Neither http nor getPage was set for", listModelName); }
|
||||
// If it is a path starting with slash, add the metaverseServer domain.
|
||||
var url = /^\//.test(endpoint) ? (Account.metaverseServerURL + endpoint) : endpoint;
|
||||
var parameters = [
|
||||
'per_page=' + itemsPerPage,
|
||||
'page=' + currentPageToRetrieve
|
||||
];
|
||||
if (searchFilter) {
|
||||
parameters.splice(parameters.length, 0, 'search=' + searchFilter);
|
||||
}
|
||||
if (sortKey) {
|
||||
parameters.splice(parameters.length, 0, 'sort=' + sortKey);
|
||||
}
|
||||
|
||||
var parametersSeparator = /\?/.test(url) ? '&' : '?';
|
||||
url = url + parametersSeparator + parameters.join('&');
|
||||
console.debug('getPage', listModelName, currentPageToRetrieve);
|
||||
http.request({uri: url}, handlePage);
|
||||
}
|
||||
|
||||
// Start the show by retrieving data according to `getPage()`.
|
||||
// It can be custom-defined by this item's Parent.
|
||||
property var getFirstPage: function (delayClear) {
|
||||
if (requestPending) {
|
||||
console.debug('deferring getFirstPage', listModelName);
|
||||
additionalFirstPageRequested = true;
|
||||
return;
|
||||
}
|
||||
delayedClear = !!delayClear;
|
||||
resetModel();
|
||||
requestPending = true;
|
||||
console.debug("getFirstPage", listModelName, currentPageToRetrieve);
|
||||
getPage();
|
||||
}
|
||||
property bool additionalFirstPageRequested: false;
|
||||
property bool requestPending: false; // For de-bouncing getNextPage.
|
||||
// This function, will get the _next_ page of data according to `getPage()`.
|
||||
// It can be custom-defined by this item's Parent. Typical usage:
|
||||
// ListView {
|
||||
// id: theList
|
||||
// model: thisPSFListModelId
|
||||
// onAtYEndChanged: if (theList.atYEnd && !theList.atYBeginning) { thisPSFListModelId.getNextPage(); }
|
||||
// ...}
|
||||
property var getNextPage: function () {
|
||||
if (requestPending || currentPageToRetrieve < 0) {
|
||||
return;
|
||||
}
|
||||
currentPageToRetrieve++;
|
||||
console.debug("getNextPage", listModelName, currentPageToRetrieve);
|
||||
requestPending = true;
|
||||
getPage();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -34,41 +34,16 @@ StackView {
|
|||
height: parent !== null ? parent.height : undefined
|
||||
property int cardWidth: 212;
|
||||
property int cardHeight: 152;
|
||||
property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/";
|
||||
property var tablet: null;
|
||||
|
||||
// This version only implements rpc(method, parameters, callback(error, result)) calls initiated from here, not initiated from .js, nor "notifications".
|
||||
property var rpcCalls: ({});
|
||||
property var rpcCounter: 0;
|
||||
RootHttpRequest { id: http; }
|
||||
signal sendToScript(var message);
|
||||
function rpc(method, parameters, callback) {
|
||||
console.debug('TabletAddressDialog: rpc: method = ', method, 'parameters = ', parameters, 'callback = ', callback)
|
||||
|
||||
rpcCalls[rpcCounter] = callback;
|
||||
var message = {method: method, params: parameters, id: rpcCounter++, jsonrpc: "2.0"};
|
||||
sendToScript(message);
|
||||
}
|
||||
function fromScript(message) {
|
||||
if (message.method === 'refreshFeeds') {
|
||||
var feeds = [happeningNow, places, snapshots];
|
||||
console.debug('TabletAddressDialog::fromScript: refreshFeeds', 'feeds = ', feeds);
|
||||
|
||||
feeds.forEach(function(feed) {
|
||||
feed.protocol = encodeURIComponent(message.protocolSignature);
|
||||
Qt.callLater(feed.fillDestinations);
|
||||
});
|
||||
|
||||
return;
|
||||
switch (message.method) {
|
||||
case 'http.response':
|
||||
http.handleHttpResponse(message);
|
||||
break;
|
||||
}
|
||||
|
||||
var callback = rpcCalls[message.id];
|
||||
if (!callback) {
|
||||
// FIXME: We often recieve very long messages here, the logging of which is drastically slowing down the main thread
|
||||
//console.log('No callback for message fromScript', JSON.stringify(message));
|
||||
return;
|
||||
}
|
||||
delete rpcCalls[message.id];
|
||||
callback(message.error, message.result);
|
||||
}
|
||||
|
||||
Component { id: tabletWebView; TabletWebView {} }
|
||||
|
@ -346,12 +321,11 @@ StackView {
|
|||
width: parent.width;
|
||||
cardWidth: 312 + (2 * 4);
|
||||
cardHeight: 163 + (2 * 4);
|
||||
metaverseServerUrl: addressBarDialog.metaverseServerUrl;
|
||||
labelText: 'HAPPENING NOW';
|
||||
actions: 'announcement';
|
||||
filter: addressLine.text;
|
||||
goFunction: goCard;
|
||||
rpc: root.rpc;
|
||||
http: http;
|
||||
}
|
||||
Feed {
|
||||
id: places;
|
||||
|
@ -359,12 +333,11 @@ StackView {
|
|||
cardWidth: 210;
|
||||
cardHeight: 110 + messageHeight;
|
||||
messageHeight: 44;
|
||||
metaverseServerUrl: addressBarDialog.metaverseServerUrl;
|
||||
labelText: 'PLACES';
|
||||
actions: 'concurrency';
|
||||
filter: addressLine.text;
|
||||
goFunction: goCard;
|
||||
rpc: root.rpc;
|
||||
http: http;
|
||||
}
|
||||
Feed {
|
||||
id: snapshots;
|
||||
|
@ -373,12 +346,11 @@ StackView {
|
|||
cardHeight: 75 + messageHeight + 4;
|
||||
messageHeight: 32;
|
||||
textPadding: 6;
|
||||
metaverseServerUrl: addressBarDialog.metaverseServerUrl;
|
||||
labelText: 'RECENT SNAPS';
|
||||
actions: 'snapshot';
|
||||
filter: addressLine.text;
|
||||
goFunction: goCard;
|
||||
rpc: root.rpc;
|
||||
http: http;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,18 @@ Item {
|
|||
return false;
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
if (openMessage != null) {
|
||||
openMessage.destroy();
|
||||
openMessage = null;
|
||||
}
|
||||
|
||||
if (openModal != null) {
|
||||
openModal.destroy();
|
||||
openModal = null;
|
||||
}
|
||||
}
|
||||
|
||||
function isUrlLoaded(url) {
|
||||
if (currentApp >= 0) {
|
||||
var currentAppUrl = tabletApps.get(currentApp).appUrl;
|
||||
|
|
|
@ -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]);
|
||||
|
@ -905,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>();
|
||||
|
@ -922,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>();
|
||||
|
@ -1108,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.";
|
||||
|
@ -1365,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.");
|
||||
|
@ -1413,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";
|
||||
|
@ -1439,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.
|
||||
|
@ -1461,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() },
|
||||
|
@ -1783,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;
|
||||
|
@ -2178,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);
|
||||
|
||||
|
@ -2249,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();
|
||||
|
@ -2395,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());
|
||||
|
||||
|
@ -2606,10 +2595,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();
|
||||
|
@ -2621,6 +2655,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()) {
|
||||
|
@ -2630,7 +2669,7 @@ void Application::initializeGL() {
|
|||
// Create the GPU backend
|
||||
|
||||
// Requires the window context, because that's what's used in the actual rendering
|
||||
// and the GPU backend will make things like the VAO which cannot be shared across
|
||||
// and the GPU backend will make things like the VAO which cannot be shared across
|
||||
// contexts
|
||||
_glWidget->makeCurrent();
|
||||
gpu::Context::init<gpu::gl::GLBackend>();
|
||||
|
@ -2653,7 +2692,7 @@ void Application::initializeDisplayPlugins() {
|
|||
auto lastActiveDisplayPluginName = activeDisplayPluginSetting.get();
|
||||
|
||||
auto defaultDisplayPlugin = displayPlugins.at(0);
|
||||
// Once time initialization code
|
||||
// Once time initialization code
|
||||
DisplayPluginPointer targetDisplayPlugin;
|
||||
foreach(auto displayPlugin, displayPlugins) {
|
||||
displayPlugin->setContext(_gpuContext);
|
||||
|
@ -2666,7 +2705,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
|
||||
|
@ -2743,40 +2782,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();
|
||||
|
@ -5429,7 +5434,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();
|
||||
}
|
||||
|
@ -5787,7 +5792,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();
|
||||
|
@ -5848,14 +5853,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");
|
||||
|
@ -6155,7 +6156,9 @@ void Application::updateWindowTitle() const {
|
|||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
||||
QString buildVersion = " (build " + applicationVersion() + ")";
|
||||
QString buildVersion = " - "
|
||||
+ (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build"))
|
||||
+ " " + applicationVersion();
|
||||
|
||||
QString loginStatus = accountManager->isLoggedIn() ? "" : " (NOT LOGGED IN)";
|
||||
|
||||
|
@ -6562,7 +6565,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()) {
|
||||
|
@ -7716,7 +7718,7 @@ void Application::sendLambdaEvent(const std::function<void()>& f) {
|
|||
} else {
|
||||
LambdaEvent event(f);
|
||||
QCoreApplication::sendEvent(this, &event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::initPlugins(const QStringList& arguments) {
|
||||
|
@ -7939,7 +7941,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>();
|
||||
|
|
|
@ -205,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -124,7 +124,7 @@ Menu::Menu() {
|
|||
});
|
||||
|
||||
// Edit > Delete
|
||||
auto deleteAction =addActionToQMenuAndActionHash(editMenu, "Delete", QKeySequence::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);
|
||||
|
@ -588,6 +588,10 @@ Menu::Menu() {
|
|||
});
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ToggleHipsFollowing, 0, false,
|
||||
avatar.get(), SLOT(setToggleHips(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawBaseOfSupport, 0, false,
|
||||
avatar.get(), SLOT(setEnableDebugDrawBaseOfSupport(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false,
|
||||
avatar.get(), SLOT(setEnableDebugDrawDefaultPose(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawAnimPose, 0, false,
|
||||
|
@ -721,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)));
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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()))) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
|
||||
#include "MyHead.h"
|
||||
#include "MySkeletonModel.h"
|
||||
#include "AnimUtil.h"
|
||||
#include "Application.h"
|
||||
#include "AvatarManager.h"
|
||||
#include "AvatarActionHold.h"
|
||||
|
@ -422,12 +423,12 @@ void MyAvatar::update(float deltaTime) {
|
|||
}
|
||||
|
||||
#ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE
|
||||
glm::vec3 p = transformPoint(getSensorToWorldMatrix(), getControllerPoseInAvatarFrame(controller::Pose::HEAD) *
|
||||
glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y));
|
||||
DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f));
|
||||
p = transformPoint(getSensorToWorldMatrix(), getHMDSensorPosition() +
|
||||
glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y));
|
||||
DebugDraw::getInstance().addMarker("facing", getOrientation(), p, glm::vec4(1.0f));
|
||||
auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
|
||||
glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation());
|
||||
glm::vec3 worldFacingAverage = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y));
|
||||
glm::vec3 worldFacing = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y));
|
||||
DebugDraw::getInstance().drawRay(worldHeadPos, worldHeadPos + worldFacing, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
|
||||
DebugDraw::getInstance().drawRay(worldHeadPos, worldHeadPos + worldFacingAverage, glm::vec4(0.0f, 0.0f, 1.0f, 1.0f));
|
||||
#endif
|
||||
|
||||
if (_goToPending) {
|
||||
|
@ -712,7 +713,8 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
|
|||
_hmdSensorOrientation = glmExtractRotation(hmdSensorMatrix);
|
||||
auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
|
||||
if (headPose.isValid()) {
|
||||
_headControllerFacing = getFacingDir2D(headPose.rotation);
|
||||
glm::quat bodyOrientation = computeBodyFacingFromHead(headPose.rotation, Vectors::UNIT_Y);
|
||||
_headControllerFacing = getFacingDir2D(bodyOrientation);
|
||||
} else {
|
||||
_headControllerFacing = glm::vec2(1.0f, 0.0f);
|
||||
}
|
||||
|
@ -1079,6 +1081,22 @@ float loadSetting(Settings& settings, const QString& name, float defaultValue) {
|
|||
return value;
|
||||
}
|
||||
|
||||
void MyAvatar::setToggleHips(bool followHead) {
|
||||
_follow.setToggleHipsFollowing(followHead);
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::setToggleHipsFollowing(bool followHead) {
|
||||
_toggleHipsFollowing = followHead;
|
||||
}
|
||||
|
||||
bool MyAvatar::FollowHelper::getToggleHipsFollowing() const {
|
||||
return _toggleHipsFollowing;
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableDebugDrawBaseOfSupport(bool isEnabled) {
|
||||
_enableDebugDrawBaseOfSupport = isEnabled;
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableDebugDrawDefaultPose(bool isEnabled) {
|
||||
_enableDebugDrawDefaultPose = isEnabled;
|
||||
|
||||
|
@ -1210,6 +1228,8 @@ void MyAvatar::loadData() {
|
|||
settings.endGroup();
|
||||
|
||||
setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible));
|
||||
_follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing));
|
||||
setEnableDebugDrawBaseOfSupport(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawBaseOfSupport));
|
||||
setEnableDebugDrawDefaultPose(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawDefaultPose));
|
||||
setEnableDebugDrawAnimPose(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawAnimPose));
|
||||
setEnableDebugDrawPosition(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawPosition));
|
||||
|
@ -2097,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
|
||||
|
@ -2410,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;
|
||||
|
||||
|
@ -2423,6 +2473,8 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings
|
|||
setModelScale(_targetScale);
|
||||
rebuildCollisionShape();
|
||||
settings.endGroup();
|
||||
|
||||
_haveReceivedHeightLimitsFromDomain = true;
|
||||
}
|
||||
|
||||
void MyAvatar::leaveDomain() {
|
||||
|
@ -2440,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) {
|
||||
|
@ -2557,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
|
||||
}
|
||||
|
@ -2567,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) {
|
||||
|
@ -2594,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;
|
||||
|
@ -2620,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.
|
||||
|
@ -2819,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);
|
||||
|
@ -2841,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;
|
||||
|
||||
|
@ -2850,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();
|
||||
}
|
||||
|
@ -2871,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;
|
||||
}
|
||||
|
@ -3014,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 {
|
||||
|
@ -3087,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);
|
||||
|
@ -3487,6 +3752,10 @@ void MyAvatar::updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose&
|
|||
}
|
||||
}
|
||||
|
||||
bool MyAvatar::isRecenteringHorizontally() const {
|
||||
return _follow.isActive(FollowHelper::Horizontal);
|
||||
}
|
||||
|
||||
const MyHead* MyAvatar::getMyHead() const {
|
||||
return static_cast<const MyHead*>(getHead());
|
||||
}
|
||||
|
|
|
@ -86,6 +86,10 @@ class MyAvatar : public Avatar {
|
|||
* @property {number} audioListenerModeCamera=1 - The audio listening position is at the camera. <em>Read-only.</em>
|
||||
* @property {number} audioListenerModeCustom=2 - The audio listening position is at a the position specified by set by the
|
||||
* <code>customListenPosition</code> and <code>customListenOrientation</code> property values. <em>Read-only.</em>
|
||||
* @property {boolean} hasScriptedBlendshapes=false - Blendshapes will be transmitted over the network if set to true.
|
||||
* @property {boolean} hasProceduralBlinkFaceMovement=true - procedural blinking will be turned on if set to true.
|
||||
* @property {boolean} hasProceduralEyeFaceMovement=true - procedural eye movement will be turned on if set to true.
|
||||
* @property {boolean} hasAudioEnabledFaceMovement=true - If set to true, voice audio will move the mouth Blendshapes while MyAvatar.hasScriptedBlendshapes is enabled.
|
||||
* @property {Vec3} customListenPosition=Vec3.ZERO - The listening position used when the <code>audioListenerMode</code>
|
||||
* property value is <code>audioListenerModeCustom</code>.
|
||||
* @property {Quat} customListenOrientation=Quat.IDENTITY - The listening orientation used when the
|
||||
|
@ -105,6 +109,9 @@ class MyAvatar : public Avatar {
|
|||
* by 30cm. <em>Read-only.</em>
|
||||
* @property {Pose} rightHandTipPose - The pose of the right hand as determined by the hand controllers, with the position
|
||||
* by 30cm. <em>Read-only.</em>
|
||||
* @property {boolean} centerOfGravityModelEnabled=true - If <code>true</code> then the avatar hips are placed according to the center of
|
||||
* gravity model that balance the center of gravity over the base of support of the feet. Setting the value <code>false</code>
|
||||
* will result in the default behaviour where the hips are placed under the head.
|
||||
* @property {boolean} hmdLeanRecenterEnabled=true - If <code>true</code> then the avatar is re-centered to be under the
|
||||
* head's position. In room-scale VR, this behavior is what causes your avatar to follow your HMD as you walk around
|
||||
* the room. Setting the value <code>false</code> is useful if you want to pin the avatar to a fixed position.
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
|
|
@ -46,32 +46,18 @@ void MyHead::simulate(float deltaTime) {
|
|||
auto player = DependencyManager::get<recording::Deck>();
|
||||
// Only use face trackers when not playing back a recording.
|
||||
if (!player->isPlaying()) {
|
||||
FaceTracker* faceTracker = qApp->getActiveFaceTracker();
|
||||
_isFaceTrackerConnected = faceTracker != nullptr && !faceTracker->isMuted();
|
||||
auto faceTracker = qApp->getActiveFaceTracker();
|
||||
const bool hasActualFaceTrackerConnected = faceTracker && !faceTracker->isMuted();
|
||||
_isFaceTrackerConnected = hasActualFaceTrackerConnected || _owningAvatar->getHasScriptedBlendshapes();
|
||||
if (_isFaceTrackerConnected) {
|
||||
_transientBlendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
|
||||
|
||||
if (typeid(*faceTracker) == typeid(DdeFaceTracker)) {
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::UseAudioForMouth)) {
|
||||
calculateMouthShapes(deltaTime);
|
||||
|
||||
const int JAW_OPEN_BLENDSHAPE = 21;
|
||||
const int MMMM_BLENDSHAPE = 34;
|
||||
const int FUNNEL_BLENDSHAPE = 40;
|
||||
const int SMILE_LEFT_BLENDSHAPE = 28;
|
||||
const int SMILE_RIGHT_BLENDSHAPE = 29;
|
||||
_transientBlendshapeCoefficients[JAW_OPEN_BLENDSHAPE] += _audioJawOpen;
|
||||
_transientBlendshapeCoefficients[SMILE_LEFT_BLENDSHAPE] += _mouth4;
|
||||
_transientBlendshapeCoefficients[SMILE_RIGHT_BLENDSHAPE] += _mouth4;
|
||||
_transientBlendshapeCoefficients[MMMM_BLENDSHAPE] += _mouth2;
|
||||
_transientBlendshapeCoefficients[FUNNEL_BLENDSHAPE] += _mouth3;
|
||||
}
|
||||
applyEyelidOffset(getFinalOrientationInWorldFrame());
|
||||
if (hasActualFaceTrackerConnected) {
|
||||
_blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
|
||||
}
|
||||
}
|
||||
|
||||
auto eyeTracker = DependencyManager::get<EyeTracker>();
|
||||
_isEyeTrackerConnected = eyeTracker->isTracking();
|
||||
// if eye tracker is connected we should get the data here.
|
||||
}
|
||||
Parent::simulate(deltaTime);
|
||||
}
|
||||
|
|
|
@ -45,7 +45,14 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
|||
return result;
|
||||
}
|
||||
|
||||
glm::mat4 hipsMat = myAvatar->deriveBodyFromHMDSensor();
|
||||
glm::mat4 hipsMat;
|
||||
if (myAvatar->getCenterOfGravityModelEnabled()) {
|
||||
// then we use center of gravity model
|
||||
hipsMat = myAvatar->deriveBodyUsingCgModel();
|
||||
} else {
|
||||
// otherwise use the default of putting the hips under the head
|
||||
hipsMat = myAvatar->deriveBodyFromHMDSensor();
|
||||
}
|
||||
glm::vec3 hipsPos = extractTranslation(hipsMat);
|
||||
glm::quat hipsRot = glmExtractRotation(hipsMat);
|
||||
|
||||
|
@ -53,8 +60,11 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
|||
glm::mat4 avatarToSensorMat = worldToSensorMat * avatarToWorldMat;
|
||||
|
||||
// dampen hips rotation, by mixing it with the avatar orientation in sensor space
|
||||
const float MIX_RATIO = 0.5f;
|
||||
hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO);
|
||||
// turning this off for center of gravity model because it is already mixed in there
|
||||
if (!(myAvatar->getCenterOfGravityModelEnabled())) {
|
||||
const float MIX_RATIO = 0.5f;
|
||||
hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO);
|
||||
}
|
||||
|
||||
if (isFlying) {
|
||||
// rotate the hips back to match the flying animation.
|
||||
|
@ -73,6 +83,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
|||
hipsPos = headPos + tiltRot * (hipsPos - headPos);
|
||||
}
|
||||
|
||||
// AJT: TODO can we remove this?
|
||||
return AnimPose(hipsRot * Quaternions::Y_180, hipsPos);
|
||||
}
|
||||
|
||||
|
@ -170,6 +181,15 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
}
|
||||
}
|
||||
|
||||
bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS);
|
||||
if (isFlying != _prevIsFlying) {
|
||||
const float FLY_TO_IDLE_HIPS_TRANSITION_TIME = 0.5f;
|
||||
_flyIdleTimer = FLY_TO_IDLE_HIPS_TRANSITION_TIME;
|
||||
} else {
|
||||
_flyIdleTimer -= deltaTime;
|
||||
}
|
||||
_prevIsFlying = isFlying;
|
||||
|
||||
// if hips are not under direct control, estimate the hips position.
|
||||
if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) {
|
||||
bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS);
|
||||
|
@ -181,14 +201,28 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
|
||||
AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying);
|
||||
|
||||
// timescale in seconds
|
||||
const float TRANS_HORIZ_TIMESCALE = 0.15f;
|
||||
const float TRANS_VERT_TIMESCALE = 0.01f; // We want the vertical component of the hips to follow quickly to prevent spine squash/stretch.
|
||||
const float ROT_TIMESCALE = 0.15f;
|
||||
const float FLY_IDLE_TRANSITION_TIMESCALE = 0.25f;
|
||||
|
||||
float transHorizAlpha, transVertAlpha, rotAlpha;
|
||||
if (_flyIdleTimer < 0.0f) {
|
||||
transHorizAlpha = glm::min(deltaTime / TRANS_HORIZ_TIMESCALE, 1.0f);
|
||||
transVertAlpha = glm::min(deltaTime / TRANS_VERT_TIMESCALE, 1.0f);
|
||||
rotAlpha = glm::min(deltaTime / ROT_TIMESCALE, 1.0f);
|
||||
} else {
|
||||
transHorizAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f);
|
||||
transVertAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f);
|
||||
rotAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f);
|
||||
}
|
||||
|
||||
// smootly lerp hips, in sensorframe, with different coeff for horiz and vertical translation.
|
||||
const float ROT_ALPHA = 0.9f;
|
||||
const float TRANS_HORIZ_ALPHA = 0.9f;
|
||||
const float TRANS_VERT_ALPHA = 0.1f;
|
||||
float hipsY = hips.trans().y;
|
||||
hips.trans() = lerp(hips.trans(), _prevHips.trans(), TRANS_HORIZ_ALPHA);
|
||||
hips.trans().y = lerp(hipsY, _prevHips.trans().y, TRANS_VERT_ALPHA);
|
||||
hips.rot() = safeLerp(hips.rot(), _prevHips.rot(), ROT_ALPHA);
|
||||
hips.trans() = lerp(_prevHips.trans(), hips.trans(), transHorizAlpha);
|
||||
hips.trans().y = lerp(_prevHips.trans().y, hipsY, transVertAlpha);
|
||||
hips.rot() = safeLerp(_prevHips.rot(), hips.rot(), rotAlpha);
|
||||
|
||||
_prevHips = hips;
|
||||
_prevHipsValid = true;
|
||||
|
|
|
@ -28,6 +28,8 @@ private:
|
|||
|
||||
AnimPose _prevHips; // sensor frame
|
||||
bool _prevHipsValid { false };
|
||||
bool _prevIsFlying { false };
|
||||
float _flyIdleTimer { 0.0f };
|
||||
|
||||
std::map<int, int> _jointRotationFrameOffsetMap;
|
||||
};
|
||||
|
|
|
@ -134,8 +134,14 @@ void Ledger::balance(const QStringList& keys) {
|
|||
keysQuery("balance", "balanceSuccess", "balanceFailure");
|
||||
}
|
||||
|
||||
void Ledger::inventory(const QStringList& keys) {
|
||||
keysQuery("inventory", "inventorySuccess", "inventoryFailure");
|
||||
void Ledger::inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage) {
|
||||
QJsonObject params;
|
||||
params["edition_filter"] = editionFilter;
|
||||
params["type_filter"] = typeFilter;
|
||||
params["title_filter"] = titleFilter;
|
||||
params["page"] = page;
|
||||
params["per_page"] = perPage;
|
||||
keysQuery("inventory", "inventorySuccess", "inventoryFailure", params);
|
||||
}
|
||||
|
||||
QString hfcString(const QJsonValue& sentValue, const QJsonValue& receivedValue) {
|
||||
|
@ -260,9 +266,9 @@ void Ledger::historyFailure(QNetworkReply& reply) {
|
|||
failResponse("history", reply);
|
||||
}
|
||||
|
||||
void Ledger::history(const QStringList& keys, const int& pageNumber) {
|
||||
void Ledger::history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage) {
|
||||
QJsonObject params;
|
||||
params["per_page"] = 100;
|
||||
params["per_page"] = itemsPerPage;
|
||||
params["page"] = pageNumber;
|
||||
keysQuery("history", "historySuccess", "historyFailure", params);
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@ public:
|
|||
void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false);
|
||||
bool receiveAt(const QString& hfc_key, const QString& signing_key);
|
||||
void balance(const QStringList& keys);
|
||||
void inventory(const QStringList& keys);
|
||||
void history(const QStringList& keys, const int& pageNumber);
|
||||
void inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage);
|
||||
void history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage);
|
||||
void account();
|
||||
void updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings = false, const bool controlledFailure = false);
|
||||
void certificateInfo(const QString& certificateId);
|
||||
|
|
|
@ -105,21 +105,21 @@ void QmlCommerce::balance() {
|
|||
}
|
||||
}
|
||||
|
||||
void QmlCommerce::inventory() {
|
||||
void QmlCommerce::inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
QStringList cachedPublicKeys = wallet->listPublicKeys();
|
||||
if (!cachedPublicKeys.isEmpty()) {
|
||||
ledger->inventory(cachedPublicKeys);
|
||||
ledger->inventory(editionFilter, typeFilter, titleFilter, page, perPage);
|
||||
}
|
||||
}
|
||||
|
||||
void QmlCommerce::history(const int& pageNumber) {
|
||||
void QmlCommerce::history(const int& pageNumber, const int& itemsPerPage) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
QStringList cachedPublicKeys = wallet->listPublicKeys();
|
||||
if (!cachedPublicKeys.isEmpty()) {
|
||||
ledger->history(cachedPublicKeys, pageNumber);
|
||||
ledger->history(cachedPublicKeys, pageNumber, itemsPerPage);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,10 +227,13 @@ QString QmlCommerce::getInstalledApps() {
|
|||
QString scriptURL = appFileJsonObject["scriptURL"].toString();
|
||||
|
||||
// If the script .app.json is on the user's local disk but the associated script isn't running
|
||||
// for some reason, start that script again.
|
||||
// for some reason (i.e. the user stopped it from Running Scripts),
|
||||
// delete the .app.json from the user's local disk.
|
||||
if (!runningScripts.contains(scriptURL)) {
|
||||
if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptURL.trimmed())).isNull()) {
|
||||
qCDebug(commerce) << "Couldn't start script while checking installed apps.";
|
||||
if (!appFile.remove()) {
|
||||
qCWarning(commerce)
|
||||
<< "Couldn't delete local .app.json file (app's script isn't running). App filename is:"
|
||||
<< appFileName;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -73,8 +73,8 @@ protected:
|
|||
|
||||
Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false);
|
||||
Q_INVOKABLE void balance();
|
||||
Q_INVOKABLE void inventory();
|
||||
Q_INVOKABLE void history(const int& pageNumber);
|
||||
Q_INVOKABLE void inventory(const QString& editionFilter = QString(), const QString& typeFilter = QString(), const QString& titleFilter = QString(), const int& page = 1, const int& perPage = 20);
|
||||
Q_INVOKABLE void history(const int& pageNumber, const int& itemsPerPage = 100);
|
||||
Q_INVOKABLE void generateKeyPair();
|
||||
Q_INVOKABLE void account();
|
||||
|
||||
|
|
|
@ -536,7 +536,6 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() {
|
|||
|
||||
// be sure to add the public key so we don't do this over and over
|
||||
_publicKeys.push_back(publicKey.toBase64());
|
||||
DependencyManager::get<WalletScriptingInterface>()->setWalletStatus((uint)WalletStatus::WALLET_STATUS_READY);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -615,7 +614,11 @@ void Wallet::updateImageProvider() {
|
|||
SecurityImageProvider* securityImageProvider;
|
||||
|
||||
// inform offscreenUI security image provider
|
||||
QQmlEngine* engine = DependencyManager::get<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);
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -1,66 +0,0 @@
|
|||
//
|
||||
// SpeechRecognitionScriptingInterface.h
|
||||
// interface/src/scripting
|
||||
//
|
||||
// Created by Trevor Berninger on 3/20/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
|
||||
//
|
||||
|
||||
#include "LimitlessVoiceRecognitionScriptingInterface.h"
|
||||
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
|
||||
#include <ThreadHelpers.h>
|
||||
|
||||
#include "InterfaceLogging.h"
|
||||
#include "ui/AvatarInputs.h"
|
||||
|
||||
const float LimitlessVoiceRecognitionScriptingInterface::_audioLevelThreshold = 0.33f;
|
||||
const int LimitlessVoiceRecognitionScriptingInterface::_voiceTimeoutDuration = 2000;
|
||||
|
||||
LimitlessVoiceRecognitionScriptingInterface::LimitlessVoiceRecognitionScriptingInterface() :
|
||||
_shouldStartListeningForVoice(false)
|
||||
{
|
||||
_voiceTimer.setSingleShot(true);
|
||||
connect(&_voiceTimer, &QTimer::timeout, this, &LimitlessVoiceRecognitionScriptingInterface::voiceTimeout);
|
||||
connect(&_connection, &LimitlessConnection::onReceivedTranscription, this, [this](QString transcription){emit onReceivedTranscription(transcription);});
|
||||
connect(&_connection, &LimitlessConnection::onFinishedSpeaking, this, [this](QString transcription){emit onFinishedSpeaking(transcription);});
|
||||
moveToNewNamedThread(&_connection, "Limitless Connection");
|
||||
}
|
||||
|
||||
void LimitlessVoiceRecognitionScriptingInterface::update() {
|
||||
const float audioLevel = AvatarInputs::getInstance()->loudnessToAudioLevel(DependencyManager::get<AudioClient>()->getAudioAverageInputLoudness());
|
||||
|
||||
if (_shouldStartListeningForVoice) {
|
||||
if (_connection._streamingAudioForTranscription) {
|
||||
if (audioLevel > _audioLevelThreshold) {
|
||||
if (_voiceTimer.isActive()) {
|
||||
_voiceTimer.stop();
|
||||
}
|
||||
} else if (!_voiceTimer.isActive()){
|
||||
_voiceTimer.start(_voiceTimeoutDuration);
|
||||
}
|
||||
} else if (audioLevel > _audioLevelThreshold) {
|
||||
// to make sure invoke doesn't get called twice before the method actually gets called
|
||||
_connection._streamingAudioForTranscription = true;
|
||||
QMetaObject::invokeMethod(&_connection, "startListening", Q_ARG(QString, authCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LimitlessVoiceRecognitionScriptingInterface::setListeningToVoice(bool listening) {
|
||||
_shouldStartListeningForVoice = listening;
|
||||
}
|
||||
|
||||
void LimitlessVoiceRecognitionScriptingInterface::setAuthKey(QString key) {
|
||||
authCode = key;
|
||||
}
|
||||
|
||||
void LimitlessVoiceRecognitionScriptingInterface::voiceTimeout() {
|
||||
if (_connection._streamingAudioForTranscription) {
|
||||
QMetaObject::invokeMethod(&_connection, "stopListening");
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
//
|
||||
// SpeechRecognitionScriptingInterface.h
|
||||
// interface/src/scripting
|
||||
//
|
||||
// Created by Trevor Berninger on 3/20/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_SpeechRecognitionScriptingInterface_h
|
||||
#define hifi_SpeechRecognitionScriptingInterface_h
|
||||
|
||||
#include <AudioClient.h>
|
||||
#include <QObject>
|
||||
#include <QFuture>
|
||||
#include "LimitlessConnection.h"
|
||||
|
||||
class LimitlessVoiceRecognitionScriptingInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
public:
|
||||
LimitlessVoiceRecognitionScriptingInterface();
|
||||
|
||||
void update();
|
||||
|
||||
QString authCode;
|
||||
|
||||
public slots:
|
||||
void setListeningToVoice(bool listening);
|
||||
void setAuthKey(QString key);
|
||||
|
||||
signals:
|
||||
void onReceivedTranscription(QString speech);
|
||||
void onFinishedSpeaking(QString speech);
|
||||
|
||||
private:
|
||||
|
||||
bool _shouldStartListeningForVoice;
|
||||
static const float _audioLevelThreshold;
|
||||
static const int _voiceTimeoutDuration;
|
||||
|
||||
QTimer _voiceTimer;
|
||||
LimitlessConnection _connection;
|
||||
|
||||
void voiceTimeout();
|
||||
};
|
||||
|
||||
#endif //hifi_SpeechRecognitionScriptingInterface_h
|
|
@ -182,7 +182,7 @@ public:
|
|||
|
||||
/**jsdoc
|
||||
* Get the list of avatars, entities, and overlays stored in a selection list.
|
||||
* @function Selection.getList
|
||||
* @function Selection.getSelectedItemsList
|
||||
* @param {string} listName - The name of the selection list.
|
||||
* @returns {Selection.SelectedItemsList} The content of a selection list. If the list name doesn't exist, the function
|
||||
* returns an empty object with no properties.
|
||||
|
@ -257,7 +257,7 @@ public:
|
|||
void onSelectedItemsListChanged(const QString& listName);
|
||||
|
||||
signals:
|
||||
/**jsoc
|
||||
/**jsdoc
|
||||
* Triggered when a list's content changes.
|
||||
* @function Selection.selectedItemsListChanged
|
||||
* @param {string} listName - The name of the selection list that changed.
|
||||
|
|
|
@ -93,10 +93,18 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) {
|
|||
static const QUrl url("dialogs/TabletConnectionFailureDialog.qml");
|
||||
auto hmd = DependencyManager::get<HMDScriptingInterface>();
|
||||
if (visible) {
|
||||
_dialogCreatedWhileShown = tablet->property("tabletShown").toBool();
|
||||
tablet->initialScreen(url);
|
||||
if (!hmd->getShouldShowTablet()) {
|
||||
hmd->openTablet();
|
||||
}
|
||||
} else if (tablet->isPathLoaded(url)) {
|
||||
tablet->closeDialog();
|
||||
tablet->gotoHomeScreen();
|
||||
if (!_dialogCreatedWhileShown) {
|
||||
hmd->closeTablet();
|
||||
}
|
||||
_dialogCreatedWhileShown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,7 @@ private:
|
|||
QPointer<OctreeStatsDialog> _octreeStatsDialog;
|
||||
QPointer<TestingDialog> _testingDialog;
|
||||
QPointer<DomainConnectionDialog> _domainConnectionDialog;
|
||||
bool _dialogCreatedWhileShown { false };
|
||||
bool _addressBarVisible { false };
|
||||
};
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#include "InterfaceLogging.h"
|
||||
|
||||
OverlayConductor::OverlayConductor() {
|
||||
|
||||
}
|
||||
|
||||
OverlayConductor::~OverlayConductor() {
|
||||
|
@ -33,8 +32,8 @@ bool OverlayConductor::headOutsideOverlay() const {
|
|||
glm::vec3 uiPos = uiTransform.getTranslation();
|
||||
glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
|
||||
|
||||
const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface.
|
||||
const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled
|
||||
const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface.
|
||||
const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled
|
||||
if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE ||
|
||||
glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) {
|
||||
return true;
|
||||
|
@ -43,10 +42,9 @@ bool OverlayConductor::headOutsideOverlay() const {
|
|||
}
|
||||
|
||||
bool OverlayConductor::updateAvatarIsAtRest() {
|
||||
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s
|
||||
const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s
|
||||
const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms
|
||||
|
||||
const float AT_REST_THRESHOLD = 0.01f;
|
||||
|
@ -69,31 +67,6 @@ bool OverlayConductor::updateAvatarIsAtRest() {
|
|||
return _currentAtRest;
|
||||
}
|
||||
|
||||
bool OverlayConductor::updateAvatarHasDriveInput() {
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms
|
||||
const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s
|
||||
|
||||
bool desiredDriving = myAvatar->hasDriveInput();
|
||||
if (desiredDriving != _desiredDriving) {
|
||||
// start timer
|
||||
_desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS);
|
||||
}
|
||||
|
||||
_desiredDriving = desiredDriving;
|
||||
|
||||
if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) {
|
||||
// timer expired
|
||||
// change state!
|
||||
_currentDriving = _desiredDriving;
|
||||
// disable timer
|
||||
_desiredDrivingTimer = 0;
|
||||
}
|
||||
|
||||
return _currentDriving;
|
||||
}
|
||||
|
||||
void OverlayConductor::centerUI() {
|
||||
// place the overlay at the current hmd position in sensor space
|
||||
auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose());
|
||||
|
@ -115,20 +88,19 @@ void OverlayConductor::update(float dt) {
|
|||
_hmdMode = false;
|
||||
}
|
||||
|
||||
bool prevDriving = _currentDriving;
|
||||
bool isDriving = updateAvatarHasDriveInput();
|
||||
bool drivingChanged = prevDriving != isDriving;
|
||||
bool isAtRest = updateAvatarIsAtRest();
|
||||
bool isMoving = !isAtRest;
|
||||
|
||||
bool shouldRecenter = false;
|
||||
|
||||
if (_flags & SuppressedByDrive) {
|
||||
if (!isDriving) {
|
||||
_flags &= ~SuppressedByDrive;
|
||||
shouldRecenter = true;
|
||||
if (_flags & SuppressedByMove) {
|
||||
if (!isMoving) {
|
||||
_flags &= ~SuppressedByMove;
|
||||
shouldRecenter = true;
|
||||
}
|
||||
} else {
|
||||
if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) {
|
||||
_flags |= SuppressedByDrive;
|
||||
if (myAvatar->getClearOverlayWhenMoving() && isMoving) {
|
||||
_flags |= SuppressedByMove;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,7 +115,6 @@ void OverlayConductor::update(float dt) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask));
|
||||
if (targetVisible != currentVisible) {
|
||||
offscreenUi->setPinned(!targetVisible);
|
||||
|
|
|
@ -23,23 +23,17 @@ public:
|
|||
|
||||
private:
|
||||
bool headOutsideOverlay() const;
|
||||
bool updateAvatarHasDriveInput();
|
||||
bool updateAvatarIsAtRest();
|
||||
|
||||
enum SupressionFlags {
|
||||
SuppressedByDrive = 0x01,
|
||||
SuppressedByMove = 0x01,
|
||||
SuppressedByHead = 0x02,
|
||||
SuppressMask = 0x03,
|
||||
};
|
||||
|
||||
uint8_t _flags { SuppressedByDrive };
|
||||
uint8_t _flags { SuppressedByMove };
|
||||
bool _hmdMode { false };
|
||||
|
||||
// used by updateAvatarHasDriveInput
|
||||
uint64_t _desiredDrivingTimer { 0 };
|
||||
bool _desiredDriving { false };
|
||||
bool _currentDriving { false };
|
||||
|
||||
// used by updateAvatarIsAtRest
|
||||
uint64_t _desiredAtRestTimer { 0 };
|
||||
bool _desiredAtRest { true };
|
||||
|
|
|
@ -35,6 +35,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
|
|||
QString thumbnailUrl = dataObject.value("thumbnail_url").toString();
|
||||
QString imageUrl = dataObject.value("image_url").toString();
|
||||
QString snapshotID = dataObject.value("id").toString();
|
||||
QString originalImageFileName = dataObject.value("original_image_file_name").toString();
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
QString placeName = _inWorldLocation.authority(); // We currently only upload shareable places, in which case this is just host.
|
||||
QString currentPath = _inWorldLocation.path();
|
||||
|
@ -48,6 +49,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
|
|||
detailsObject.insert("shareable_url", dataObject.value("shareable_url").toString());
|
||||
}
|
||||
detailsObject.insert("snapshot_id", snapshotID);
|
||||
detailsObject.insert("original_image_file_name", originalImageFileName);
|
||||
QString pickledDetails = QJsonDocument(detailsObject).toJson();
|
||||
userStoryObject.insert("details", pickledDetails);
|
||||
userStoryObject.insert("thumbnail_url", thumbnailUrl);
|
||||
|
|
|
@ -21,19 +21,31 @@ UpdateDialog::UpdateDialog(QQuickItem* parent) :
|
|||
OffscreenQmlDialog(parent)
|
||||
{
|
||||
auto applicationUpdater = DependencyManager::get<AutoUpdater>();
|
||||
int currentVersion = QCoreApplication::applicationVersion().toInt();
|
||||
int latestVersion = applicationUpdater.data()->getBuildData().lastKey();
|
||||
_updateAvailableDetails = "v" + QString::number(latestVersion) + " released on "
|
||||
+ QString(applicationUpdater.data()->getBuildData()[latestVersion]["releaseTime"]).replace(" ", " ");
|
||||
if (applicationUpdater) {
|
||||
|
||||
_releaseNotes = "";
|
||||
for (int i = latestVersion; i > currentVersion; i--) {
|
||||
if (applicationUpdater.data()->getBuildData().contains(i)) {
|
||||
QString releaseNotes = applicationUpdater.data()->getBuildData()[i]["releaseNotes"];
|
||||
releaseNotes.remove("<br />");
|
||||
releaseNotes.remove(QRegExp("^\n+"));
|
||||
_releaseNotes += "\n" + QString().sprintf("%d", i) + "\n" + releaseNotes + "\n";
|
||||
auto buildData = applicationUpdater.data()->getBuildData();
|
||||
ApplicationVersion latestVersion = buildData.lastKey();
|
||||
_updateAvailableDetails = "v" + latestVersion.versionString + " released on "
|
||||
+ QString(buildData[latestVersion]["releaseTime"]).replace(" ", " ");
|
||||
|
||||
_releaseNotes = "";
|
||||
|
||||
auto it = buildData.end();
|
||||
while (it != buildData.begin()) {
|
||||
--it;
|
||||
|
||||
if (applicationUpdater->getCurrentVersion() < it.key()) {
|
||||
// grab the release notes for this later version
|
||||
QString releaseNotes = it.value()["releaseNotes"];
|
||||
releaseNotes.remove("<br />");
|
||||
releaseNotes.remove(QRegExp("^\n+"));
|
||||
_releaseNotes += "\n" + it.key().versionString + "\n" + releaseNotes + "\n";
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,5 +59,5 @@ const QString& UpdateDialog::releaseNotes() const {
|
|||
|
||||
void UpdateDialog::triggerUpgrade() {
|
||||
auto applicationUpdater = DependencyManager::get<AutoUpdater>();
|
||||
applicationUpdater.data()->performAutoUpdate(applicationUpdater.data()->getBuildData().lastKey());
|
||||
applicationUpdater.data()->openLatestUpdateURL();
|
||||
}
|
||||
|
|
|
@ -283,7 +283,7 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
|
|||
}
|
||||
|
||||
bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -69,11 +69,11 @@ public:
|
|||
virtual QVariant getProperty(const QString& property) override;
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal);
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false);
|
||||
|
||||
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) {
|
||||
return findRayIntersection(origin, direction, distance, face, surfaceNormal);
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) {
|
||||
return findRayIntersection(origin, direction, distance, face, surfaceNormal, precisionPicking);
|
||||
}
|
||||
|
||||
virtual SpatialParentTree* getParentTree() const override;
|
||||
|
|
|
@ -521,7 +521,7 @@ QVariant Circle3DOverlay::getProperty(const QString& property) {
|
|||
}
|
||||
|
||||
bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) {
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
|
||||
// Scale the dimensions by the diameter
|
||||
glm::vec2 dimensions = getOuterRadius() * 2.0f * getDimensions();
|
||||
|
|
|
@ -55,7 +55,7 @@ public:
|
|||
void setMinorTickMarksColor(const xColor& value) { _minorTickMarksColor = value; }
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) override;
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
|
||||
virtual Circle3DOverlay* createClone() const override;
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public:
|
|||
virtual Grid3DOverlay* createClone() const override;
|
||||
|
||||
// Grids are UI tools, and may not be intersected (pickable)
|
||||
virtual bool findRayIntersection(const glm::vec3&, const glm::vec3&, float&, BoxFace&, glm::vec3&) override { return false; }
|
||||
virtual bool findRayIntersection(const glm::vec3&, const glm::vec3&, float&, BoxFace&, glm::vec3&, bool precisionPicking = false) override { return false; }
|
||||
|
||||
protected:
|
||||
Transform evalRenderTransform() override;
|
||||
|
|
|
@ -258,7 +258,7 @@ void Image3DOverlay::setURL(const QString& url) {
|
|||
}
|
||||
|
||||
bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
if (_texture && _texture->isLoaded()) {
|
||||
// Make sure position and rotation is updated.
|
||||
Transform transform = getTransform();
|
||||
|
|
|
@ -43,7 +43,7 @@ public:
|
|||
bool isTransparent() override { return Base3DOverlay::isTransparent() || _alphaTexture; }
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) override;
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
|
||||
virtual Image3DOverlay* createClone() const override;
|
||||
|
||||
|
|
|
@ -100,29 +100,40 @@ void ModelOverlay::update(float deltatime) {
|
|||
processMaterials();
|
||||
emit DependencyManager::get<scriptable::ModelProviderFactory>()->modelAddedToScene(getID(), NestableType::Overlay, _model);
|
||||
}
|
||||
bool metaDirty = false;
|
||||
if (_visibleDirty) {
|
||||
_visibleDirty = false;
|
||||
// don't show overlays in mirrors or spectator-cam unless _isVisibleInSecondaryCamera is true
|
||||
uint8_t modelRenderTagMask = (_isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW);
|
||||
_model->setTagMask(modelRenderTagMask, scene);
|
||||
_model->setVisibleInScene(getVisible(), scene);
|
||||
metaDirty = true;
|
||||
}
|
||||
if (_drawInFrontDirty) {
|
||||
_drawInFrontDirty = false;
|
||||
_model->setLayeredInFront(getDrawInFront(), scene);
|
||||
metaDirty = true;
|
||||
}
|
||||
if (_drawInHUDDirty) {
|
||||
_drawInHUDDirty = false;
|
||||
_model->setLayeredInHUD(getDrawHUDLayer(), scene);
|
||||
metaDirty = true;
|
||||
}
|
||||
if (_groupCulledDirty) {
|
||||
_groupCulledDirty = false;
|
||||
_model->setGroupCulled(_isGroupCulled);
|
||||
_model->setGroupCulled(_isGroupCulled, scene);
|
||||
metaDirty = true;
|
||||
}
|
||||
if (metaDirty) {
|
||||
transaction.updateItem<Overlay>(getRenderItemID(), [](Overlay& data) {});
|
||||
}
|
||||
scene->enqueueTransaction(transaction);
|
||||
|
||||
if (!_texturesLoaded && _model->getGeometry() && _model->getGeometry()->areTexturesLoaded()) {
|
||||
_texturesLoaded = true;
|
||||
if (!_modelTextures.isEmpty()) {
|
||||
_model->setTextures(_modelTextures);
|
||||
}
|
||||
_model->updateRenderItems();
|
||||
}
|
||||
}
|
||||
|
@ -221,8 +232,7 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
|
|||
if (texturesValue.isValid() && texturesValue.canConvert(QVariant::Map)) {
|
||||
_texturesLoaded = false;
|
||||
QVariantMap textureMap = texturesValue.toMap();
|
||||
QMetaObject::invokeMethod(_model.get(), "setTextures", Qt::AutoConnection,
|
||||
Q_ARG(const QVariantMap&, textureMap));
|
||||
_modelTextures = textureMap;
|
||||
}
|
||||
|
||||
auto groupCulledValue = properties["isGroupCulled"];
|
||||
|
@ -499,16 +509,16 @@ QVariant ModelOverlay::getProperty(const QString& property) {
|
|||
}
|
||||
|
||||
bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
|
||||
QVariantMap extraInfo;
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking);
|
||||
}
|
||||
|
||||
bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) {
|
||||
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking);
|
||||
}
|
||||
|
||||
ModelOverlay* ModelOverlay::createClone() const {
|
||||
|
|
|
@ -45,9 +45,9 @@ public:
|
|||
void setProperties(const QVariantMap& properties) override;
|
||||
QVariant getProperty(const QString& property) override;
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) override;
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) override;
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override;
|
||||
|
||||
virtual ModelOverlay* createClone() const override;
|
||||
|
||||
|
|
|
@ -554,7 +554,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
|
|||
glm::vec3 thisSurfaceNormal;
|
||||
QVariantMap thisExtraInfo;
|
||||
if (thisOverlay->findRayIntersectionExtraInfo(ray.origin, ray.direction, thisDistance,
|
||||
thisFace, thisSurfaceNormal, thisExtraInfo)) {
|
||||
thisFace, thisSurfaceNormal, thisExtraInfo, precisionPicking)) {
|
||||
bool isDrawInFront = thisOverlay->getDrawInFront();
|
||||
if ((bestIsFront && isDrawInFront && thisDistance < bestDistance)
|
||||
|| (!bestIsFront && (isDrawInFront || thisDistance < bestDistance))) {
|
||||
|
|
|
@ -71,7 +71,7 @@ QVariant Planar3DOverlay::getProperty(const QString& property) {
|
|||
}
|
||||
|
||||
bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
// FIXME - face and surfaceNormal not being returned
|
||||
return findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), getDimensions(), distance);
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue