3
0
Fork 0
mirror of https://github.com/lubosz/overte.git synced 2025-04-27 01:55:31 +02:00

Merge remote-tracking branch 'upstream/master' into script-engine-thread-agent

This commit is contained in:
Simon Walton 2018-07-31 15:54:54 -07:00
commit ecfba312ef
241 changed files with 7141 additions and 2797 deletions
android/app/src/main
assignment-client/src
cmake/externals
domain-server
interface

View file

@ -22,7 +22,8 @@
android:icon="@drawable/ic_launcher"
android:launchMode="singleTop"
android:roundIcon="@drawable/ic_launcher">
<activity android:name="io.highfidelity.hifiinterface.PermissionChecker">
<activity android:name="io.highfidelity.hifiinterface.PermissionChecker"
android:theme="@style/Theme.AppCompat.Translucent.NoActionBar.Launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View file

@ -162,7 +162,19 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea
jvmArgs.version = JNI_VERSION_1_6; // choose your JNI version
jvmArgs.name = NULL; // you might want to give the java thread a name
jvmArgs.group = NULL; // you might want to assign the java thread to a ThreadGroup
jvm->AttachCurrentThread(reinterpret_cast<JNIEnv **>(&myNewEnv), &jvmArgs);
int attachedHere = 0; // know if detaching at the end is necessary
jint res = jvm->GetEnv((void**)&myNewEnv, JNI_VERSION_1_6); // checks if current env needs attaching or it is already attached
if (JNI_OK != res) {
qDebug() << "[JCRASH] GetEnv env not attached yet, attaching now..";
res = jvm->AttachCurrentThread(reinterpret_cast<JNIEnv **>(&myNewEnv), &jvmArgs);
if (JNI_OK != res) {
qDebug() << "[JCRASH] Failed to AttachCurrentThread, ErrorCode = " << res;
return;
} else {
attachedHere = 1;
}
}
QAndroidJniObject string = QAndroidJniObject::fromString(a);
jboolean jBackToScene = (jboolean) backToScene;
@ -175,7 +187,9 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea
myNewEnv->CallObjectMethod(hashmap, mapClassPut, QAndroidJniObject::fromString("url").object<jstring>(), jArg.object<jstring>());
}
__interfaceActivity.callMethod<void>("openAndroidActivity", "(Ljava/lang/String;ZLjava/util/HashMap;)V", string.object<jstring>(), jBackToScene, hashmap);
jvm->DetachCurrentThread();
if (attachedHere) {
jvm->DetachCurrentThread();
}
});
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::hapticFeedbackRequested, [](int duration) {

View file

@ -8,6 +8,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import org.json.JSONException;
import org.json.JSONObject;
@ -135,4 +136,13 @@ public class PermissionChecker extends Activity {
launchActivityWithPermissions();
}
}
@Override
protected void onResume() {
super.onResume();
View decorView = getWindow().getDecorView();
// Hide the status bar.
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
}
}

View file

@ -4,6 +4,7 @@ import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
@ -32,6 +33,7 @@ public class HomeFragment extends Fragment {
private OnHomeInteractionListener mListener;
private SwipeRefreshLayout mSwipeRefreshLayout;
public native String nativeGetLastLocation();
@ -57,12 +59,14 @@ public class HomeFragment extends Fragment {
View rootView = inflater.inflate(R.layout.fragment_home, container, false);
searchNoResultsView = rootView.findViewById(R.id.searchNoResultsView);
mSwipeRefreshLayout = rootView.findViewById(R.id.swipeRefreshLayout);
mDomainsView = rootView.findViewById(R.id.rvDomains);
int numberOfColumns = 1;
GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns);
mDomainsView.setLayoutManager(gridLayoutMgr);
mDomainAdapter = new DomainAdapter(getContext(), HifiUtils.getInstance().protocolVersionSignature(), nativeGetLastLocation());
mSwipeRefreshLayout.setRefreshing(true);
mDomainAdapter.setClickListener((view, position, domain) -> {
new Handler(getActivity().getMainLooper()).postDelayed(() -> {
if (mListener != null) {
@ -76,12 +80,14 @@ public class HomeFragment extends Fragment {
searchNoResultsView.setText(R.string.search_no_results);
searchNoResultsView.setVisibility(View.VISIBLE);
mDomainsView.setVisibility(View.GONE);
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onNonEmptyAdapter() {
searchNoResultsView.setVisibility(View.GONE);
mDomainsView.setVisibility(View.VISIBLE);
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
@ -104,7 +110,7 @@ public class HomeFragment extends Fragment {
@Override
public void afterTextChanged(Editable editable) {
mDomainAdapter.loadDomains(editable.toString());
mDomainAdapter.loadDomains(editable.toString(), false);
if (editable.length() > 0) {
mSearchIconView.setVisibility(View.GONE);
mClearSearch.setVisibility(View.VISIBLE);
@ -130,6 +136,13 @@ public class HomeFragment extends Fragment {
mClearSearch.setOnClickListener(view -> onSearchClear(view));
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
mDomainAdapter.loadDomains(mSearchView.getText().toString(), true);
}
});
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
return rootView;

View file

@ -10,7 +10,7 @@ import io.highfidelity.hifiinterface.view.DomainAdapter;
public interface DomainProvider {
void retrieve(String filterText, DomainCallback domainCallback);
void retrieve(String filterText, DomainCallback domainCallback, boolean forceRefresh);
interface DomainCallback {
void retrieveOk(List<DomainAdapter.Domain> domain);

View file

@ -49,8 +49,8 @@ public class UserStoryDomainProvider implements DomainProvider {
}
@Override
public synchronized void retrieve(String filterText, DomainCallback domainCallback) {
if (!startedToGetFromAPI) {
public synchronized void retrieve(String filterText, DomainCallback domainCallback, boolean forceRefresh) {
if (!startedToGetFromAPI || forceRefresh) {
startedToGetFromAPI = true;
fillDestinations(filterText, domainCallback);
} else {
@ -72,6 +72,7 @@ public class UserStoryDomainProvider implements DomainProvider {
allStories.clear();
getUserStoryPage(1, allStories, null,
ex -> {
suggestions.clear();
allStories.forEach(userStory -> {
if (taggedStoriesIds.contains(userStory.id)) {
userStory.tagFound = true;

View file

@ -42,14 +42,14 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
mProtocol = protocol;
mLastLocation = lastLocation;
domainProvider = new UserStoryDomainProvider(mProtocol);
loadDomains("");
loadDomains("", true);
}
public void setListener(AdapterListener adapterListener) {
mAdapterListener = adapterListener;
}
public void loadDomains(String filterText) {
public void loadDomains(String filterText, boolean forceRefresh) {
domainProvider.retrieve(filterText, new DomainProvider.DomainCallback() {
@Override
public void retrieveOk(List<Domain> domain) {
@ -76,7 +76,7 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
Log.e("DOMAINS", message, e);
if (mAdapterListener != null) mAdapterListener.onError(e, message);
}
});
}, forceRefresh);
}
private void overrideDefaultThumbnails(List<Domain> domain) {

View file

@ -60,6 +60,8 @@ import android.view.View;
import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import org.qtproject.qt5.android.QtNative;
@SuppressWarnings("unused")
public class QtActivity extends Activity {
public String APPLICATION_PARAMETERS = null;
@ -103,7 +105,7 @@ public class QtActivity extends Activity {
}
public boolean super_dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
return super_dispatchPopulateAccessibilityEvent(event);
return super.dispatchPopulateAccessibilityEvent(event);
}
//---------------------------------------------------------------------------
@ -362,7 +364,25 @@ public class QtActivity extends Activity {
@Override
protected void onDestroy() {
super.onDestroy();
QtApplication.invokeDelegate();
/*
cduarte https://highfidelity.manuscript.com/f/cases/16712/App-freezes-on-opening-randomly
After Qt upgrade to 5.11 we had a black screen crash after closing the application with
the hardware button "Back" and trying to start the app again. It could only be fixed after
totally closing the app swiping it in the list of running apps.
This problem did not happen with the previous Qt version.
After analysing changes we came up with this case and change:
https://codereview.qt-project.org/#/c/218882/
In summary they've moved libs loading to the same thread as main() and as a matter of correctness
in the onDestroy method in QtActivityDelegate, they exit that thread with `QtNative.m_qtThread.exit();`
That exit call is the main reason of this problem.
In this fix we just replace the `QtApplication.invokeDelegate();` call that may end using the
entire onDestroy method including that thread exit line for other three lines that purposely
terminate qt (borrowed from QtActivityDelegate::onDestroy as well).
*/
QtNative.terminateQt();
QtNative.setActivity(null, null);
System.exit(0);
}
//---------------------------------------------------------------------------

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
<item android:drawable="@android:color/black"/>
<item android:drawable="@drawable/hifi_logo_splash"
android:gravity="center_horizontal"
android:top="225dp"
android:height="242dp"
android:width="242dp">
</item>
</layer-list>

View file

@ -7,9 +7,17 @@
android:layout_height="match_parent"
android:background="@android:color/black">
<ImageView
android:id="@+id/hifi_logo"
android:layout_width="242dp"
android:layout_height="242dp"
android:src="@drawable/hifi_logo_splash"
android:layout_centerHorizontal="true"
android:layout_marginTop="225dp"/>
<ProgressBar
style="@style/Widget.AppCompat.ProgressBar"
android:theme="@style/HifiCircularProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/hifi_logo"
android:layout_centerHorizontal="true"/>
</RelativeLayout>

View file

@ -63,13 +63,18 @@
android:visibility="gone"
/>
<android.support.v7.widget.RecyclerView
android:id="@+id/rvDomains"
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
app:layout_constraintTop_toBottomOf="@id/searchView"
app:layout_constraintBottom_toBottomOf="@id/contentHomeRoot"
app:layout_constraintStart_toStartOf="@id/contentHomeRoot"
app:layout_constraintEnd_toEndOf="@id/contentHomeRoot"
android:layout_width="0dp"
android:layout_height="0dp" />
android:layout_height="0dp">
<android.support.v7.widget.RecyclerView
android:id="@+id/rvDomains"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.constraint.ConstraintLayout>

View file

@ -17,4 +17,5 @@
<color name="colorLoginError">#FF7171</color>
<color name="black_060">#99000000</color>
<color name="statusbar_color">#292929</color>
<color name="hifiLogoColor">#23B2E7</color>
</resources>

View file

@ -19,6 +19,9 @@
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.AppCompat.Translucent.NoActionBar.Launcher" parent="Theme.AppCompat.Translucent.NoActionBar">
<item name="android:windowBackground">@drawable/launch_screen</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
@ -62,4 +65,7 @@
<item name="android:background">@color/white_opaque</item>
</style>
<style name="HifiCircularProgress" parent="Theme.AppCompat.Light">
<item name="colorAccent">@color/hifiLogoColor</item>
</style>
</resources>

View file

@ -50,6 +50,7 @@
#include "entities/AssignmentParentFinder.h"
#include "RecordingScriptingInterface.h"
#include "AbstractAudioInterface.h"
#include "AgentScriptingInterface.h"
static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10;
@ -344,8 +345,6 @@ void Agent::scriptRequestFinished() {
void Agent::executeScript() {
_scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload);
DependencyManager::get<RecordingScriptingInterface>()->setScriptEngine(_scriptEngine);
// setup an Avatar for the script to use
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
@ -452,7 +451,7 @@ void Agent::executeScript() {
packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket");
// register ourselves to the script engine
_scriptEngine->registerGlobalObject("Agent", this);
_scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this));
_scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
_scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());

View file

@ -33,19 +33,6 @@
#include "entities/EntityTreeHeadlessViewer.h"
#include "avatars/ScriptableAvatar.h"
/**jsdoc
* @namespace Agent
*
* @hifi-assignment-client
*
* @property {boolean} isAvatar
* @property {boolean} isPlayingAvatarSound <em>Read-only.</em>
* @property {boolean} isListeningToAudioStream
* @property {boolean} isNoiseGateEnabled
* @property {number} lastReceivedAudioLoudness <em>Read-only.</em>
* @property {Uuid} sessionUUID <em>Read-only.</em>
*/
class Agent : public ThreadedAssignment {
Q_OBJECT
@ -73,28 +60,11 @@ public:
virtual void aboutToFinish() override;
public slots:
/**jsdoc
* @function Agent.run
* @deprecated This function is being removed from the API.
*/
void run() override;
/**jsdoc
* @function Agent.playAvatarSound
* @param {object} avatarSound
*/
void playAvatarSound(SharedSoundPointer avatarSound);
/**jsdoc
* @function Agent.setIsAvatar
* @param {boolean} isAvatar
*/
void setIsAvatar(bool isAvatar);
/**jsdoc
* @function Agent.isAvatar
* @returns {boolean}
*/
void setIsAvatar(bool isAvatar);
bool isAvatar() const { return _isAvatar; }
/**jsdoc

View file

@ -0,0 +1,17 @@
//
// AgentScriptingInterface.cpp
// assignment-client/src
//
// Created by Thijs Wenker on 7/23/18.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AgentScriptingInterface.h"
AgentScriptingInterface::AgentScriptingInterface(Agent* agent) :
QObject(agent),
_agent(agent)
{ }

View file

@ -0,0 +1,80 @@
//
// AgentScriptingInterface.h
// assignment-client/src
//
// Created by Thijs Wenker on 7/23/18.
// 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
//
#pragma once
#ifndef hifi_AgentScriptingInterface_h
#define hifi_AgentScriptingInterface_h
#include <QObject>
#include "Agent.h"
/**jsdoc
* @namespace Agent
*
* @hifi-assignment-client
*
* @property {boolean} isAvatar
* @property {boolean} isPlayingAvatarSound <em>Read-only.</em>
* @property {boolean} isListeningToAudioStream
* @property {boolean} isNoiseGateEnabled
* @property {number} lastReceivedAudioLoudness <em>Read-only.</em>
* @property {Uuid} sessionUUID <em>Read-only.</em>
*/
class AgentScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar)
Q_PROPERTY(bool isPlayingAvatarSound READ isPlayingAvatarSound)
Q_PROPERTY(bool isListeningToAudioStream READ isListeningToAudioStream WRITE setIsListeningToAudioStream)
Q_PROPERTY(bool isNoiseGateEnabled READ isNoiseGateEnabled WRITE setIsNoiseGateEnabled)
Q_PROPERTY(float lastReceivedAudioLoudness READ getLastReceivedAudioLoudness)
Q_PROPERTY(QUuid sessionUUID READ getSessionUUID)
public:
AgentScriptingInterface(Agent* agent);
bool isPlayingAvatarSound() const { return _agent->isPlayingAvatarSound(); }
bool isListeningToAudioStream() const { return _agent->isListeningToAudioStream(); }
void setIsListeningToAudioStream(bool isListeningToAudioStream) const { _agent->setIsListeningToAudioStream(isListeningToAudioStream); }
bool isNoiseGateEnabled() const { return _agent->isNoiseGateEnabled(); }
void setIsNoiseGateEnabled(bool isNoiseGateEnabled) const { _agent->setIsNoiseGateEnabled(isNoiseGateEnabled); }
float getLastReceivedAudioLoudness() const { return _agent->getLastReceivedAudioLoudness(); }
QUuid getSessionUUID() const { return _agent->getSessionUUID(); }
public slots:
/**jsdoc
* @function Agent.setIsAvatar
* @param {boolean} isAvatar
*/
void setIsAvatar(bool isAvatar) const { _agent->setIsAvatar(isAvatar); }
/**jsdoc
* @function Agent.isAvatar
* @returns {boolean}
*/
bool isAvatar() const { return _agent->isAvatar(); }
/**jsdoc
* @function Agent.playAvatarSound
* @param {object} avatarSound
*/
void playAvatarSound(SharedSoundPointer avatarSound) const { _agent->playAvatarSound(avatarSound); }
private:
Agent* _agent;
};
#endif // hifi_AgentScriptingInterface_h

View file

@ -23,6 +23,7 @@
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QJsonDocument>
#include <QtCore/QSaveFile>
#include <QtCore/QString>
#include <QtGui/QImageReader>
#include <QtCore/QVector>
@ -1042,7 +1043,7 @@ bool AssetServer::loadMappingsFromFile() {
bool AssetServer::writeMappingsToFile() {
auto mapFilePath = _resourcesDirectory.absoluteFilePath(MAP_FILE_NAME);
QFile mapFile { mapFilePath };
QSaveFile mapFile { mapFilePath };
if (mapFile.open(QIODevice::WriteOnly)) {
QJsonObject root;
@ -1053,8 +1054,12 @@ bool AssetServer::writeMappingsToFile() {
QJsonDocument jsonDocument { root };
if (mapFile.write(jsonDocument.toJson()) != -1) {
qCDebug(asset_server) << "Wrote JSON mappings to file at" << mapFilePath;
return true;
if (mapFile.commit()) {
qCDebug(asset_server) << "Wrote JSON mappings to file at" << mapFilePath;
return true;
} else {
qCWarning(asset_server) << "Failed to commit JSON mappings to file at" << mapFilePath;
}
} else {
qCWarning(asset_server) << "Failed to write JSON mappings to file at" << mapFilePath;
}

View file

@ -522,11 +522,8 @@ void EntityServer::startDynamicDomainVerification() {
qCDebug(entities) << "Entity passed dynamic domain verification:" << entityID;
}
} else {
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityID
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; NOT deleting entity" << entityID
<< "More info:" << jsonObject;
tree->withWriteLock([&] {
tree->deleteEntity(entityID, true);
});
}
networkReply->deleteLater();

View file

@ -3,7 +3,7 @@ set(EXTERNAL_NAME GifCreator)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://hifi-public.s3.amazonaws.com/dependencies/GifCreator.zip
URL https://public.highfidelity.com/dependencies/GifCreator.zip
URL_MD5 8ac8ef5196f47c658dce784df5ecdb70
CONFIGURE_COMMAND ""
BUILD_COMMAND ""

View file

@ -17,7 +17,7 @@ if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.26.0_public.zip
URL https://public.highfidelity.com/dependencies/ovr_sdk_win_1.26.0_public.zip
URL_MD5 06804ff9727b910dcd04a37c800053b5
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/LibOVRCMakeLists.txt" <SOURCE_DIR>/CMakeLists.txt
@ -38,7 +38,7 @@ elseif(APPLE)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://static.oculus.com/sdk-downloads/ovr_sdk_macos_0.5.0.1.tar.gz
URL https://public.highfidelity.com/dependencies/ovr_sdk_macos_0.5.0.1.tar.gz
URL_MD5 0a0785a04fb285f64f62267388344ad6
CONFIGURE_COMMAND ""
BUILD_COMMAND ""

View file

@ -9,7 +9,7 @@ if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/OVRPlatformSDK_v1.10.0.zip
URL https://public.highfidelity.com/dependencies/OVRPlatformSDK_v1.10.0.zip
URL_MD5 e6c8264af16d904e6506acd5172fa0a9
CONFIGURE_COMMAND ""
BUILD_COMMAND ""

View file

@ -5,7 +5,7 @@ include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
#URL https://github.com/boostorg/config/archive/boost-1.58.0.zip
URL http://hifi-public.s3.amazonaws.com/dependencies/config-boost-1.58.0.zip
URL https://public.highfidelity.com/dependencies/config-boost-1.58.0.zip
URL_MD5 42fa673bae2b7645a22736445e80eb8d
CONFIGURE_COMMAND ""
BUILD_COMMAND ""

View file

@ -17,7 +17,7 @@ include(ExternalProject)
if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.88.tgz
URL https://public.highfidelity.com/dependencies/bullet-2.88.tgz
URL_MD5 0a6876607ebe83e227427215f15946fd
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_BULLET3=0 -DBUILD_OPENGL3_DEMOS=0 -DBUILD_BULLET2_DEMOS=0 -DBUILD_UNIT_TESTS=0 -DUSE_GLUT=0 -DUSE_DX11=0
LOG_DOWNLOAD 1
@ -28,7 +28,7 @@ if (WIN32)
else ()
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.88.tgz
URL https://public.highfidelity.com/dependencies/bullet-2.88.tgz
URL_MD5 0a6876607ebe83e227427215f15946fd
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_BULLET3=0 -DBUILD_OPENGL3_DEMOS=0 -DBUILD_BULLET2_DEMOS=0 -DBUILD_UNIT_TESTS=0 -DUSE_GLUT=0
LOG_DOWNLOAD 1

View file

@ -6,7 +6,7 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://public.highfidelity.com/dependencies/crashpad_062317.1.zip
URL https://public.highfidelity.com/dependencies/crashpad_062317.1.zip
URL_MD5 9c84b77f5f23daf939da1371825ed2b1
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
@ -25,7 +25,7 @@ if (WIN32)
elseif (APPLE)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://public.highfidelity.com/dependencies/crashpad_mac_070318.zip
URL https://public.highfidelity.com/dependencies/crashpad_mac_070318.zip
URL_MD5 ba1501dc163591ac2d1be74946967e2a
CONFIGURE_COMMAND ""
BUILD_COMMAND ""

View file

@ -11,7 +11,7 @@ endif ()
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/draco-1.1.0.zip
URL https://public.highfidelity.com/dependencies/draco-1.1.0.zip
URL_MD5 208f8b04c91d5f1c73d731a3ea37c5bb
CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>-$<CONFIG> ${EXTRA_CMAKE_FLAGS}
LOG_DOWNLOAD 1

View file

@ -15,7 +15,7 @@ include(ExternalProject)
# that would override CMAKE_CXX_FLAGS
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://hifi-public.s3.amazonaws.com/dependencies/etc2comp-patched.zip
URL https://public.highfidelity.com/dependencies/etc2comp-patched.zip
URL_MD5 4c96153eb179acbe619e3d99d3330595
CMAKE_ARGS ${ANDROID_CMAKE_ARGS} ${EXTRA_CMAKE_FLAGS}
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build

View file

@ -5,7 +5,7 @@ include(SelectLibraryConfigurations)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://hifi-public.s3.amazonaws.com/austin/glad/glad32es.zip
URL https://public.highfidelity.com/dependencies/glad/glad32es.zip
URL_MD5 6a641d8c49dee4c895fa59315f5682a6
CONFIGURE_COMMAND CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_POSITION_INDEPENDENT_CODE=ON
LOG_DOWNLOAD 1

View file

@ -5,7 +5,7 @@ include(SelectLibraryConfigurations)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://hifi-public.s3.amazonaws.com/austin/glad/glad41.zip
URL https://public.highfidelity.com/dependencies/glad/glad41.zip
URL_MD5 1324eeec33abe91e67d19ae551ba624d
CONFIGURE_COMMAND CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_POSITION_INDEPENDENT_CODE=ON
LOG_DOWNLOAD 1

View file

@ -5,7 +5,7 @@ include(SelectLibraryConfigurations)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://hifi-public.s3.amazonaws.com/austin/glad/glad45.zip
URL https://public.highfidelity.com/dependencies/glad/glad45.zip
URL_MD5 cfb19b3cb5b2f8f1d1669fb3150e5f05
CONFIGURE_COMMAND CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_POSITION_INDEPENDENT_CODE=ON
LOG_DOWNLOAD 1

View file

@ -3,7 +3,7 @@ set(EXTERNAL_NAME gli)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://hifi-public.s3.amazonaws.com/dependencies/gli-0.8.1.0.zip
URL https://public.highfidelity.com/dependencies/gli-0.8.1.0.zip
URL_MD5 00c990f59c12bbf367956ef399d6f798
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
CONFIGURE_COMMAND ""

View file

@ -3,7 +3,7 @@ set(EXTERNAL_NAME glm)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.8.5-patched.zip
URL https://public.highfidelity.com/dependencies/glm-0.9.8.5-patched.zip
URL_MD5 7d39ecc1cea275427534c3cfd6dd63f0
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> ${EXTERNAL_ARGS}

View file

@ -6,16 +6,16 @@ set(EXTERNAL_NAME hifiAudioCodec)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
if (WIN32)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-win-2.0.zip)
set(DOWNLOAD_URL https://public.highfidelity.com/dependencies/codecSDK-win-2.0.zip)
set(DOWNLOAD_MD5 9199d4dbd6b16bed736b235efe980e67)
elseif (APPLE)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-mac-2.0.zip)
set(DOWNLOAD_URL https://public.highfidelity.com/dependencies/codecSDK-mac-2.0.zip)
set(DOWNLOAD_MD5 21649881e7d5dc94f922179be96f76ba)
elseif (ANDROID)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-android-2.0.zip)
set(DOWNLOAD_URL https://public.highfidelity.com/dependencies/codecSDK-android-2.0.zip)
set(DOWNLOAD_MD5 aef2a852600d498d58aa586668191683)
elseif (UNIX)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux-2.0.zip)
set(DOWNLOAD_URL https://public.highfidelity.com/dependencies/codecSDK-linux-2.0.zip)
set(DOWNLOAD_MD5 67fb7755f9bcafb98a9fceea53bc7481)
else()
return()

View file

@ -4,7 +4,7 @@ set(EXTERNAL_NAME neuron)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
set(NEURON_URL "https://s3.amazonaws.com/hifi-public/dependencies/neuron_datareader_b.12.2.zip")
set(NEURON_URL "https://public.highfidelity.com/dependencies/neuron_datareader_b.12.2.zip")
set(NEURON_URL_MD5 "84273ad2200bf86a9279d1f412a822ca")
ExternalProject_Add(${EXTERNAL_NAME}

View file

@ -8,7 +8,7 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://s3.amazonaws.com/hifi-public/dependencies/nvtt-win-2.1.0.hifi.zip
URL https://public.highfidelity.com/dependencies/nvtt-win-2.1.0.hifi.zip
URL_MD5 10da01cf601f88f6dc12a6bc13c89136
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
@ -29,7 +29,7 @@ else ()
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.hifi-83462e4.zip
URL https://public.highfidelity.com/dependencies/nvidia-texture-tools-2.1.0.hifi-83462e4.zip
URL_MD5 602776e08515b54bfa1b8dc455003f0f
CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_POSITION_INDEPENDENT_CODE=ON
LOG_DOWNLOAD 1

View file

@ -7,7 +7,7 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://github.com/ValveSoftware/openvr/archive/v1.0.6.zip
URL https://public.highfidelity.com/dependencies/openvr-1.0.6.zip
URL_MD5 f6892cd3a3078f505d03b4297f5a1951
CONFIGURE_COMMAND ""
BUILD_COMMAND ""

View file

@ -3,7 +3,7 @@ set(EXTERNAL_NAME polyvox)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/polyvox-master-2015-7-15.zip
URL https://public.highfidelity.com/dependencies/polyvox-master-2015-7-15.zip
URL_MD5 9ec6323b87e849ae36e562ae1c7494a9
CMAKE_ARGS -DENABLE_EXAMPLES=OFF -DENABLE_BINDINGS=OFF -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build

View file

@ -13,7 +13,7 @@ endif ()
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://hifi-public.s3.amazonaws.com/dependencies/quazip-0.7.3.zip
URL https://public.highfidelity.com/dependencies/quazip-0.7.3.zip
URL_MD5 ed03754d39b9da1775771819b8001d45
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
CMAKE_ARGS ${QUAZIP_CMAKE_ARGS}

View file

@ -7,7 +7,7 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/SDL2-devel-2.0.3-VC.zip
URL https://public.highfidelity.com/dependencies/SDL2-devel-2.0.3-VC.zip
URL_MD5 30a333bcbe94bc5016e8799c73e86233
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
@ -18,7 +18,8 @@ elseif (APPLE)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/SDL2-2.0.3.zip
URL https://public.highfidelity.com/dependencies/SDL2-2.0.3.zip
URL_MD5 55f1eae5142d20db11c844d8d4d6deed
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DVIDEO_OPENGL=OFF
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
LOG_DOWNLOAD 1
@ -49,7 +50,7 @@ else ()
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://www.libsdl.org/release/SDL2-2.0.3.tar.gz
URL https://public.highfidelity.com/dependencies/SDL2-2.0.3.tar.gz
URL_MD5 fe6c61d2e9df9ef570e7e80c6e822537
CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
LOG_DOWNLOAD 1

View file

@ -4,15 +4,15 @@ set(EXTERNAL_NAME sixense)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
#set(SIXENSE_URL "http://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_062612.zip")
#set(SIXENSE_URL "https://public.highfidelity.com/dependencies/SixenseSDK_062612.zip")
#set(SIXENSE_URL_MD5 "10cc8dc470d2ac1244a88cf04bc549cc")
#set(SIXENSE_NEW_LAYOUT 0)
set(SIXENSE_URL "http://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_071615.zip")
set(SIXENSE_URL "https://public.highfidelity.com/dependencies/SixenseSDK_071615.zip")
set(SIXENSE_URL_MD5 "752a3901f334124e9cffc2ba4136ef7d")
set(SIXENSE_NEW_LAYOUT 1)
#set(SIXENSE_URL "http://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_102215.zip")
#set(SIXENSE_URL "https://public.highfidelity.com/dependencies/SixenseSDK_102215.zip")
#set(SIXENSE_URL_MD5 "93c3a6795cce777a0f472b09532935f1")
#set(SIXENSE_NEW_LAYOUT 1)

View file

@ -4,7 +4,7 @@ set(EXTERNAL_NAME steamworks)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
set(STEAMWORKS_URL "https://s3.amazonaws.com/hifi-public/dependencies/steamworks_sdk_137.zip")
set(STEAMWORKS_URL "https://public.highfidelity.com/dependencies/steamworks_sdk_137.zip")
set(STEAMWORKS_URL_MD5 "95ba9d0e3ddc04f8a8be17d2da806cbb")
ExternalProject_Add(

View file

@ -3,13 +3,13 @@ set(EXTERNAL_NAME tbb)
include(ExternalProject)
if (WIN32)
set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb2017_20170604oss_win_slim.zip)
set(DOWNLOAD_URL https://public.highfidelity.com/dependencies/tbb2017_20170604oss_win_slim.zip)
set(DOWNLOAD_MD5 065934458e3db88397f3d10e7eea536c)
elseif (APPLE)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb2017_20170604oss_mac_slim.tar.gz)
set(DOWNLOAD_URL https://public.highfidelity.com/dependencies/tbb2017_20170604oss_mac_slim.tar.gz)
set(DOWNLOAD_MD5 62bde626b396f8e1a85c6a8ded1d8105)
else ()
set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb2017_20170604oss_lin_slim.tar.gz)
set(DOWNLOAD_URL https://public.highfidelity.com/dependencies/tbb2017_20170604oss_lin_slim.tar.gz)
set(DOWNLOAD_MD5 2a5c721f40fa3503ffc12c18dd00011c)
endif ()

View file

@ -7,7 +7,7 @@ endif ()
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/v-hacd-master.zip
URL https://public.highfidelity.com/dependencies/v-hacd-master.zip
URL_MD5 3bfc94f8dd3dfbfe8f4dc088f4820b3e
CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build

View file

@ -6,7 +6,7 @@ if (WIN32)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi10.zip
URL https://public.highfidelity.com/dependencies/qtaudio_wasapi10.zip
URL_MD5 4f40e49715a420fb67b45b9cee19052c
CONFIGURE_COMMAND ""
BUILD_COMMAND ""

View file

@ -5,7 +5,8 @@ include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/zlib128.zip
URL https://public.highfidelity.com/dependencies/zlib128.zip
URL_MD5 126f8676442ffbd97884eb4d6f32afb4
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
LOG_DOWNLOAD 1

View file

@ -1223,7 +1223,7 @@
"name": "max_avatar_height",
"type": "double",
"label": "Maximum Avatar Height (meters)",
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.",
"help": "Limits the height of avatars in your domain. Cannot be greater than 1755.",
"placeholder": 5.2,
"default": 5.2
},

View file

@ -58,7 +58,11 @@ $(document).ready(function(){
}
Settings.handlePostSettings = function(formJSON) {
if (!verifyAvatarHeights()) {
return false;
}
// check if we've set the basic http password
if (formJSON["security"]) {
@ -207,7 +211,7 @@ $(document).ready(function(){
swal({
title: '',
type: 'error',
text: "There was a problem retreiving domain information from High Fidelity API.",
text: "There was a problem retrieving domain information from High Fidelity API.",
confirmButtonText: 'Try again',
showCancelButton: true,
closeOnConfirm: false
@ -288,7 +292,7 @@ $(document).ready(function(){
swal({
title: 'Create new domain ID',
type: 'input',
text: 'Enter a label this machine.</br></br>This will help you identify which domain ID belongs to which machine.</br></br>',
text: 'Enter a label for this machine.</br></br>This will help you identify which domain ID belongs to which machine.</br></br>',
showCancelButton: true,
confirmButtonText: "Create",
closeOnConfirm: false,
@ -669,7 +673,7 @@ $(document).ready(function(){
var spinner = createDomainSpinner();
$('#' + Settings.PLACES_TABLE_ID).after($(spinner));
var errorEl = createDomainLoadingError("There was an error retreiving your places.");
var errorEl = createDomainLoadingError("There was an error retrieving your places.");
$("#" + Settings.PLACES_TABLE_ID).after(errorEl);
// do we have a domain ID?
@ -1091,4 +1095,43 @@ $(document).ready(function(){
$('#settings_backup .panel-body').html(html);
}
function verifyAvatarHeights() {
var errorString = '';
var minAllowedHeight = 0.009;
var maxAllowedHeight = 1755;
var alertCss = { backgroundColor: '#ffa0a0' };
var minHeightElement = $('input[name="avatars.min_avatar_height"]');
var maxHeightElement = $('input[name="avatars.max_avatar_height"]');
var minHeight = Number(minHeightElement.val());
var maxHeight = Number(maxHeightElement.val());
if (maxHeight < minHeight) {
errorString = 'Maximum avatar height must not be less than minimum avatar height<br>';
minHeightElement.css(alertCss);
maxHeightElement.css(alertCss);
};
if (minHeight < minAllowedHeight) {
errorString += 'Minimum avatar height must not be less than ' + minAllowedHeight + '<br>';
minHeightElement.css(alertCss);
}
if (maxHeight > maxAllowedHeight) {
errorString += 'Maximum avatar height must not be greater than ' + maxAllowedHeight + '<br>';
maxHeightElement.css(alertCss);
}
if (errorString.length > 0) {
swal({
type: 'error',
title: '',
text: errorString,
html: true
});
return false;
} else {
return true;
}
}
});

View file

@ -1916,14 +1916,16 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
// don't handle if we don't have a matching node
if (!matchingNode) {
return false;
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
return true;
}
auto nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
// don't handle if we don't have node data for this node
if (!nodeData) {
return false;
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
return true;
}
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
@ -1944,7 +1946,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
}
// request not handled
return false;
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
return true;
}
// check if this is a request for our domain ID

Binary file not shown.

Before

(image error) Size: 126 KiB

After

(image error) Size: 125 KiB

View file

@ -26,6 +26,7 @@ ScrollingWindow {
y: 100
Component.onCompleted: {
focus = true
shown = true
addressBar.text = webview.url
}

View file

@ -22,6 +22,7 @@ Original.Button {
property int fontSize: hifi.fontSizes.buttonLabel
property alias implicitTextWidth: buttonText.implicitWidth
property string buttonGlyph: "";
property int fontCapitalization: Font.AllUppercase
width: hifi.dimensions.buttonWidth
height: hifi.dimensions.controlLineHeight
@ -107,7 +108,7 @@ Original.Button {
RalewayBold {
id: buttonText;
anchors.centerIn: parent;
font.capitalization: Font.AllUppercase
font.capitalization: control.fontCapitalization
color: enabled ? hifi.buttons.textColor[control.color]
: hifi.buttons.disabledTextColor[control.colorScheme]
size: control.fontSize

View file

@ -46,7 +46,6 @@ FocusScope {
hoverEnabled: true
visible: true
height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control.
textRole: "text"
function previousItem() { root.currentHighLightedIndex = (root.currentHighLightedIndex + comboBox.count - 1) % comboBox.count; }
function nextItem() { root.currentHighLightedIndex = (root.currentHighLightedIndex + comboBox.count + 1) % comboBox.count; }

View file

@ -244,7 +244,7 @@ Rectangle {
var avatarSettings = {
dominantHand : settings.dominantHandIsLeft ? 'left' : 'right',
collisionsEnabled : settings.avatarCollisionsOn,
animGraphUrl : settings.avatarAnimationJSON,
animGraphOverrideUrl : settings.avatarAnimationOverrideJSON,
collisionSoundUrl : settings.avatarCollisionSoundUrl
};
@ -480,7 +480,7 @@ Rectangle {
MouseArea {
anchors.fill: parent
onClicked: {
popup.showSpecifyAvatarUrl(function() {
popup.showSpecifyAvatarUrl(currentAvatar.avatarUrl, function() {
var url = popup.inputText.text;
emitSendToScript({'method' : 'applyExternalAvatar', 'avatarURL' : url})
}, function(link) {
@ -682,6 +682,14 @@ Rectangle {
PropertyChanges { target: container; y: -5 }
PropertyChanges { target: favoriteAvatarImage; dropShadowRadius: 10 }
PropertyChanges { target: favoriteAvatarImage; dropShadowVerticalOffset: 6 }
},
State {
name: "getMoreAvatarsHovered"
when: getMoreAvatarsMouseArea.containsMouse;
PropertyChanges { target: getMoreAvatarsMouseArea; anchors.bottomMargin: -5 }
PropertyChanges { target: container; y: -5 }
PropertyChanges { target: getMoreAvatarsImage; dropShadowRadius: 10 }
PropertyChanges { target: getMoreAvatarsImage; dropShadowVerticalOffset: 6 }
}
]
@ -741,6 +749,7 @@ Rectangle {
}
ShadowRectangle {
id: getMoreAvatarsImage
width: 92
height: 92
radius: 5
@ -756,7 +765,9 @@ Rectangle {
}
MouseArea {
id: getMoreAvatarsMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
popup.showBuyAvatars(function() {

View file

@ -53,7 +53,7 @@ Column {
'protocol=' + encodeURIComponent(Window.protocolSignature())
];
endpoint: '/api/v1/user_stories?' + options.join('&');
itemsPerPage: 3;
itemsPerPage: 4;
processPage: function (data) {
return data.user_stories.map(makeModelData);
};
@ -106,7 +106,6 @@ Column {
highlightMoveDuration: -1;
highlightMoveVelocity: -1;
currentIndex: -1;
onAtXEndChanged: { if (scroll.atXEnd && !scroll.atXBeginning) { suggestions.getNextPage(); } }
spacing: 12;
width: parent.width;

View file

@ -61,7 +61,7 @@ Rectangle {
'username';
}
sortAscending: connectionsTable.sortIndicatorOrder === Qt.AscendingOrder;
itemsPerPage: 9;
itemsPerPage: 10;
listView: connectionsTable;
processPage: function (data) {
return data.users.map(function (user) {
@ -145,6 +145,22 @@ Rectangle {
}
pal.sendToScript({method: 'refreshNearby', params: params});
}
function refreshConnections() {
var flickable = connectionsUserModel.flickable;
connectionsRefreshScrollTimer.oldY = flickable.contentY;
flickable.contentY = 0;
connectionsUserModel.getFirstPage('delayRefresh', function () {
connectionsRefreshScrollTimer.start();
});
}
Timer {
id: connectionsRefreshScrollTimer;
interval: 500;
property real oldY: 0;
onTriggered: {
connectionsUserModel.flickable.contentY = oldY;
}
}
Rectangle {
id: palTabContainer;
@ -276,7 +292,10 @@ Rectangle {
id: reloadConnections;
width: reloadConnections.height;
glyph: hifi.glyphs.reload;
onClicked: connectionsUserModel.getFirstPage('delayRefresh');
onClicked: {
pal.sendToScript({method: 'refreshConnections'});
refreshConnections();
}
}
}
// "CONNECTIONS" text
@ -786,14 +805,6 @@ Rectangle {
}
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.
rowDelegate: Rectangle {
@ -1108,7 +1119,7 @@ Rectangle {
function findNearbySessionIndex(sessionId, optionalData) { // no findIndex in .qml
var data = optionalData || nearbyUserModelData, length = data.length;
for (var i = 0; i < length; i++) {
if (data[i].sessionId === sessionId) {
if (data[i].sessionId === sessionId.toString()) {
return i;
}
}
@ -1120,7 +1131,7 @@ Rectangle {
var data = message.params;
var index = -1;
iAmAdmin = Users.canKick;
index = findNearbySessionIndex('', data);
index = findNearbySessionIndex("", data);
if (index !== -1) {
myData = data[index];
data.splice(index, 1);
@ -1197,8 +1208,8 @@ Rectangle {
for (var userId in message.params) {
var audioLevel = message.params[userId][0];
var avgAudioLevel = message.params[userId][1];
// If the userId is 0, we're updating "myData".
if (userId == 0) {
// If the userId is "", we're updating "myData".
if (userId === "") {
myData.audioLevel = audioLevel;
myCard.audioLevel = audioLevel; // Defensive programming
myData.avgAudioLevel = avgAudioLevel;
@ -1217,6 +1228,9 @@ Rectangle {
case 'clearLocalQMLData':
ignored = {};
break;
case 'refreshConnections':
refreshConnections();
break;
case 'avatarDisconnected':
var sessionID = message.params[0];
delete ignored[sessionID];

View file

@ -14,6 +14,7 @@ Rectangle {
property string titleText: ''
property string bodyText: ''
property alias inputText: input;
property alias dialogButtons: buttons
property string imageSource: null
onImageSourceChanged: {
@ -36,6 +37,7 @@ Rectangle {
function close() {
visible = false;
dialogButtons.yesButton.fontCapitalization = Font.AllUppercase;
onButton1Clicked = null;
onButton2Clicked = null;
button1text = '';

View file

@ -3,7 +3,7 @@ import QtQuick 2.5
MessageBox {
id: popup
function showSpecifyAvatarUrl(callback, linkCallback) {
function showSpecifyAvatarUrl(url, callback, linkCallback) {
popup.onButton2Clicked = callback;
popup.titleText = 'Specify Avatar URL'
popup.bodyText = 'This will not overwrite your existing favorite if you are wearing one.<br>' +
@ -12,6 +12,8 @@ MessageBox {
'</a>'
popup.inputText.visible = true;
popup.inputText.placeholderText = 'Enter Avatar Url';
popup.inputText.text = url;
popup.inputText.selectAll();
popup.button1text = 'CANCEL';
popup.button2text = 'CONFIRM';
@ -35,11 +37,12 @@ MessageBox {
function showGetWearables(callback, linkCallback) {
popup.button2text = 'AvatarIsland'
popup.dialogButtons.yesButton.fontCapitalization = Font.MixedCase;
popup.button1text = 'CANCEL'
popup.titleText = 'Get Wearables'
popup.bodyText = 'Buy wearables from <a href="app://marketplace">Marketplace</a>' + '<br/>' +
'Wear wearables from <a href="app://purchases">My Purchases</a>' + '<br/>' +
'You can visit the domain “AvatarIsland” to get wearables'
popup.bodyText = 'Buy wearables from <b><a href="app://marketplace">Marketplace.</a></b>' + '<br/>' +
'Wear wearables from <b><a href="app://purchases">My Purchases.</a></b>' + '<br/>' + '<br/>' +
'Visit “AvatarIsland” to get wearables'
popup.imageSource = getWearablesUrl;
popup.onButton2Clicked = function() {
@ -94,12 +97,13 @@ MessageBox {
function showBuyAvatars(callback, linkCallback) {
popup.button2text = 'BodyMart'
popup.dialogButtons.yesButton.fontCapitalization = Font.MixedCase;
popup.button1text = 'CANCEL'
popup.titleText = 'Get Avatars'
popup.bodyText = 'Buy avatars from <a href="app://marketplace">Marketplace</a>' + '<br/>' +
'Wear avatars from <a href="app://purchases">My Purchases</a>' + '<br/>' +
'You can visit the domain “BodyMart” to get avatars'
popup.bodyText = 'Buy avatars from <b><a href="app://marketplace">Marketplace.</a></b>' + '<br/>' +
'Wear avatars from <b><a href="app://purchases">My Purchases.</a></b>' + '<br/>' + '<br/>' +
'Visit “BodyMart” to get free avatars.'
popup.imageSource = getAvatarsUrl;
popup.onButton2Clicked = function() {

View file

@ -20,7 +20,8 @@ Rectangle {
property real scaleValue: scaleSlider.value / 10
property alias dominantHandIsLeft: leftHandRadioButton.checked
property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked
property alias avatarAnimationJSON: avatarAnimationUrlInputText.text
property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text
property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText
property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text
property real avatarScaleBackup;
@ -45,6 +46,7 @@ Rectangle {
}
avatarAnimationJSON = settings.animGraphUrl;
avatarAnimationOverrideJSON = settings.animGraphOverrideUrl;
avatarCollisionSoundUrl = settings.collisionSoundUrl;
visible = true;

View file

@ -398,7 +398,7 @@ Item {
http: root.http;
listModelName: root.listModelName;
endpoint: "/api/v1/users?filter=connections";
itemsPerPage: 8;
itemsPerPage: 9;
listView: connectionsList;
processPage: function (data) {
return data.users;
@ -520,7 +520,6 @@ Item {
visible: !connectionsLoading.visible;
clip: true;
model: connectionsModel;
onAtYEndChanged: if (connectionsList.atYEnd && !connectionsList.atYBeginning) { connectionsModel.getNextPage(); }
snapMode: ListView.SnapToItem;
// Anchors
anchors.fill: parent;

View file

@ -328,7 +328,16 @@ Item {
item.buttonColor = "#E2334D";
item.buttonClicked = function() {
sendToPurchases({ method: 'flipCard', closeAll: true });
sendToPurchases({method: 'updateItemClicked', itemId: root.itemId, itemEdition: root.itemEdition, upgradeUrl: root.upgradeUrl});
sendToPurchases({
method: 'updateItemClicked',
itemId: root.itemId,
itemEdition: root.itemEdition,
upgradeUrl: root.upgradeUrl,
itemHref: root.itemHref,
itemType: root.itemType,
isInstalled: root.isInstalled,
wornEntityID: root.wornEntityID
});
}
}
}
@ -723,7 +732,7 @@ Item {
}
HiFiGlyphs {
id: rezIcon;
text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
text: root.isInstalled ? "" : (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
anchors.right: rezIconLabel.left;
anchors.rightMargin: 2;
anchors.verticalCenter: parent.verticalCenter;

View file

@ -551,8 +551,9 @@ Rectangle {
HifiModels.PSFListModel {
id: purchasesModel;
itemsPerPage: 6;
itemsPerPage: 7;
listModelName: 'purchases';
listView: purchasesContentsList;
getPage: function () {
console.debug('getPage', purchasesModel.listModelName, root.isShowingMyItems, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage);
Commerce.inventory(
@ -706,7 +707,58 @@ Rectangle {
}
}
} else if (msg.method === "updateItemClicked") {
sendToScript(msg);
// These three cases are very similar to the conditionals below, under
// "if msg.method === "giftAsset". They differ in their popup's wording
// and the actions to take when continuing.
// I could see an argument for DRYing up this code, but I think the
// actions are different enough now and potentially moving forward such that I'm
// OK with "somewhat repeating myself".
if (msg.itemType === "app" && msg.isInstalled) {
lightboxPopup.titleText = "Uninstall App";
lightboxPopup.bodyText = "The app that you are trying to update is installed.<br><br>" +
"If you proceed, the current version of the app will be uninstalled.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = function() {
Commerce.uninstallApp(msg.itemHref);
sendToScript(msg);
};
lightboxPopup.visible = true;
} else if (msg.itemType === "wearable" && msg.wornEntityID !== '') {
lightboxPopup.titleText = "Remove Wearable";
lightboxPopup.bodyText = "You are currently wearing the wearable that you are trying to update.<br><br>" +
"If you proceed, this wearable will be removed.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = function() {
Entities.deleteEntity(msg.wornEntityID);
purchasesModel.setProperty(index, 'wornEntityID', '');
sendToScript(msg);
};
lightboxPopup.visible = true;
} else if (msg.itemType === "avatar" && MyAvatar.skeletonModelURL === msg.itemHref) {
lightboxPopup.titleText = "Change Avatar to Default";
lightboxPopup.bodyText = "You are currently wearing the avatar that you are trying to update.<br><br>" +
"If you proceed, your avatar will be changed to the default avatar.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = function() {
MyAvatar.useFullAvatarURL('');
sendToScript(msg);
};
lightboxPopup.visible = true;
} else {
sendToScript(msg);
}
} else if (msg.method === "giftAsset") {
sendAsset.assetName = msg.itemName;
sendAsset.assetCertID = msg.certId;
@ -765,14 +817,6 @@ Rectangle {
}
}
}
onAtYEndChanged: {
if (purchasesContentsList.atYEnd && !purchasesContentsList.atYBeginning) {
console.log("User scrolled to the bottom of 'Purchases'.");
purchasesModel.getNextPage();
}
}
}
Rectangle {

View file

@ -212,6 +212,7 @@ Item {
HifiModels.PSFListModel {
id: transactionHistoryModel;
listModelName: "transaction history"; // For debugging. Alternatively, we could specify endpoint for that purpose, even though it's not used directly.
listView: transactionHistory;
itemsPerPage: 6;
getPage: function () {
console.debug('getPage', transactionHistoryModel.listModelName, transactionHistoryModel.currentPageToRetrieve);
@ -346,12 +347,6 @@ Item {
}
}
}
onAtYEndChanged: {
if (transactionHistory.atYEnd && !transactionHistory.atYBeginning) {
console.log("User scrolled to the bottom of 'Recent Activity'.");
transactionHistoryModel.getNextPage();
}
}
}
Item {

View file

@ -33,7 +33,6 @@ ListModel {
// 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'); }
@ -60,11 +59,41 @@ ListModel {
// 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.
property var listView; // Optional. For debugging, or for having the scroll handler automatically call getNextPage.
property var flickable: listView && (listView.flickableItem || listView);
// 2: get two pages before you need it (i.e. one full page before you reach the end).
// 1: equivalent to paging when reaching end (and not before).
// 0: don't getNextPage on scroll at all here. The application code will do it.
property real pageAhead: 2.0;
function needsEarlyYFetch() {
return flickable
&& !flickable.atYBeginning
&& (flickable.contentY - flickable.originY) >= (flickable.contentHeight - (pageAhead * flickable.height));
}
function needsEarlyXFetch() {
return flickable
&& !flickable.atXBeginning
&& (flickable.contentX - flickable.originX) >= (flickable.contentWidth - (pageAhead * flickable.width));
}
function getNextPageIfHorizontalScroll() {
if (needsEarlyXFetch()) { getNextPage(); }
}
function getNextPageIfVerticalScroll() {
if (needsEarlyYFetch()) { getNextPage(); }
}
Component.onCompleted: {
initialized = true;
if (flickable && pageAhead > 0.0) {
// Pun: Scrollers are usually one direction or another, such that only one of the following will actually fire.
flickable.contentXChanged.connect(getNextPageIfHorizontalScroll);
flickable.contentYChanged.connect(getNextPageIfVerticalScroll);
}
}
property int totalPages: 0;
property int totalEntries: 0;
// Check consistency and call processPage.
function handlePage(error, response) {
function handlePage(error, response, cb) {
var processed;
console.debug('handlePage', listModelName, additionalFirstPageRequested, error, JSON.stringify(response));
function fail(message) {
@ -105,7 +134,9 @@ ListModel {
if (additionalFirstPageRequested) {
console.debug('deferred getFirstPage', listModelName);
additionalFirstPageRequested = false;
getFirstPage('delayedClear');
getFirstPage('delayedClear', cb);
} else if (cb) {
cb();
}
}
function debugView(label) {
@ -118,7 +149,7 @@ ListModel {
// 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.
property var getPage: function (cb) { // 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;
@ -136,12 +167,12 @@ ListModel {
var parametersSeparator = /\?/.test(url) ? '&' : '?';
url = url + parametersSeparator + parameters.join('&');
console.debug('getPage', listModelName, currentPageToRetrieve);
http.request({uri: url}, handlePage);
http.request({uri: url}, cb ? function (error, result) { handlePage(error, result, cb); } : 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) {
property var getFirstPage: function (delayClear, cb) {
if (requestPending) {
console.debug('deferring getFirstPage', listModelName);
additionalFirstPageRequested = true;
@ -151,7 +182,7 @@ ListModel {
resetModel();
requestPending = true;
console.debug("getFirstPage", listModelName, currentPageToRetrieve);
getPage();
getPage(cb);
}
property bool additionalFirstPageRequested: false;
property bool requestPending: false; // For de-bouncing getNextPage.

View file

@ -299,7 +299,7 @@ Item {
anchors.fill: stackView
id: controllerPrefereneces
objectName: "TabletControllerPreferences"
showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"]
showCategories: [( (HMD.active) ? "VR Movement" : "Movement"), "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"]
categoryProperties: {
"VR Movement" : {
"User real-world height (meters)" : { "anchors.right" : "undefined" },

View file

@ -3650,6 +3650,10 @@ bool Application::event(QEvent* event) {
bool Application::eventFilter(QObject* object, QEvent* event) {
if (_aboutToQuit) {
return true;
}
if (event->type() == QEvent::Leave) {
getApplicationCompositor().handleLeaveEvent();
}
@ -4839,6 +4843,7 @@ void Application::loadSettings() {
}
isFirstPerson = (qApp->isHMDMode());
} else {
// if this is not the first run, the camera will be initialized differently depending on user settings
@ -5286,7 +5291,7 @@ void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm
_keyboardFocusHighlight->setPulseMin(0.5);
_keyboardFocusHighlight->setPulseMax(1.0);
_keyboardFocusHighlight->setColorPulse(1.0);
_keyboardFocusHighlight->setIgnoreRayIntersection(true);
_keyboardFocusHighlight->setIgnorePickIntersection(true);
_keyboardFocusHighlight->setDrawInFront(false);
_keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight);
}
@ -6190,6 +6195,9 @@ PickRay Application::computePickRay(float x, float y) const {
getApplicationCompositor().computeHmdPickRay(pickPoint, result.origin, result.direction);
} else {
pickPoint /= getCanvasSize();
if (_myCamera.getMode() == CameraMode::CAMERA_MODE_MIRROR) {
pickPoint.x = 1.0f - pickPoint.x;
}
QMutexLocker viewLocker(&_viewMutex);
_viewFrustum.computePickRay(pickPoint.x, pickPoint.y, result.origin, result.direction);
}
@ -6525,9 +6533,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
entityScriptingInterface->setPacketSender(&_entityEditSender);
entityScriptingInterface->setEntityTree(getEntities()->getTree());
// give the script engine to the RecordingScriptingInterface for its callbacks
DependencyManager::get<RecordingScriptingInterface>()->setScriptEngine(scriptEngine);
if (property(hifi::properties::TEST).isValid()) {
scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance());
}

View file

@ -144,9 +144,19 @@ void AvatarBookmarks::removeBookmark(const QString& bookmarkName) {
emit bookmarkDeleted(bookmarkName);
}
bool isWearableEntity(const EntityItemPointer& entity) {
return entity->isVisible() && entity->getParentJointIndex() != INVALID_JOINT_INDEX &&
(entity->getParentID() == DependencyManager::get<NodeList>()->getSessionUUID() || entity->getParentID() == DependencyManager::get<AvatarManager>()->getMyAvatar()->getSelfID());
}
void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
myAvatar->removeAvatarEntities();
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
myAvatar->removeAvatarEntities([&](const QUuid& entityID) {
auto entity = entityTree->findEntityByID(entityID);
return entity && isWearableEntity(entity);
});
addAvatarEntities(avatarEntities);
}
@ -163,7 +173,12 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) {
QVariantMap bookmark = bookmarkEntry.value().toMap();
if (!bookmark.empty()) {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
myAvatar->removeAvatarEntities();
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
myAvatar->removeAvatarEntities([&](const QUuid& entityID) {
auto entity = entityTree->findEntityByID(entityID);
return entity && isWearableEntity(entity);
});
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
myAvatar->useFullAvatarURL(avatarUrl);
qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
@ -233,6 +248,27 @@ QVariantMap AvatarBookmarks::getAvatarDataToBookmark() {
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
QScriptEngine scriptEngine;
QVariantList wearableEntities;
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
auto avatarEntities = myAvatar->getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
auto entity = entityTree->findEntityByID(entityID);
if (!entity || !isWearableEntity(entity)) {
continue;
}
QVariantMap avatarEntityData;
EncodeBitstreamParams params;
auto desiredProperties = entity->getEntityProperties(params);
desiredProperties += PROP_LOCAL_POSITION;
desiredProperties += PROP_LOCAL_ROTATION;
EntityItemProperties entityProperties = entity->getProperties(desiredProperties);
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
avatarEntityData["properties"] = scriptProperties.toVariant();
wearableEntities.append(QVariant(avatarEntityData));
}
bookmark.insert(ENTRY_AVATAR_ENTITIES, wearableEntities);
return bookmark;
}

View file

@ -275,6 +275,13 @@ Menu::Menu() {
QString("hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog");
});
// Settings > Attachments...
action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Attachments);
connect(action, &QAction::triggered, [] {
qApp->showDialog(QString("hifi/dialogs/AttachmentsDialog.qml"),
QString("hifi/tablet/TabletAttachmentsDialog.qml"), "AttachmentsDialog");
});
// Settings > Developer Menu
addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menu", 0, false, this, SLOT(toggleDeveloperMenus()));

View file

@ -14,6 +14,7 @@
#include <string>
#include <QScriptEngine>
#include <QtCore/QJsonDocument>
#include "AvatarLogging.h"
@ -617,6 +618,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
result.intersects = true;
result.avatarID = avatar->getID();
result.distance = distance;
result.face = face;
result.surfaceNormal = surfaceNormal;
result.extraInfo = extraInfo;
}
}
@ -628,6 +631,79 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
return result;
}
ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector(const PickParabola& pick,
const QVector<EntityItemID>& avatarsToInclude,
const QVector<EntityItemID>& avatarsToDiscard) {
ParabolaToAvatarIntersectionResult result;
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(const_cast<AvatarManager*>(this), "findParabolaIntersectionVector",
Q_RETURN_ARG(ParabolaToAvatarIntersectionResult, result),
Q_ARG(const PickParabola&, pick),
Q_ARG(const QVector<EntityItemID>&, avatarsToInclude),
Q_ARG(const QVector<EntityItemID>&, avatarsToDiscard));
return result;
}
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()))) {
continue;
}
float parabolicDistance;
BoxFace face;
glm::vec3 surfaceNormal;
SkeletonModelPointer avatarModel = avatar->getSkeletonModel();
// It's better to intersect the parabola against the avatar's actual mesh, but this is currently difficult to
// do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code
// intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking
// against the avatar is sort-of right, but you likely wont be able to pick against the arms.
// TODO -- find a way to extract transformed avatar mesh data from the rendering engine.
// if we weren't picking against the capsule, we would want to pick against the avatarBounds...
// AABox avatarBounds = avatarModel->getRenderableMeshBound();
// if (!avatarBounds.findParabolaIntersection(pick.origin, pick.velocity, pick.acceleration, parabolicDistance, face, surfaceNormal)) {
// // parabola doesn't intersect avatar's bounding-box
// continue;
// }
glm::vec3 start;
glm::vec3 end;
float radius;
avatar->getCapsule(start, end, radius);
bool intersects = findParabolaCapsuleIntersection(pick.origin, pick.velocity, pick.acceleration, start, end, radius, avatar->getWorldOrientation(), parabolicDistance);
if (!intersects) {
// ray doesn't intersect avatar's capsule
continue;
}
QVariantMap extraInfo;
intersects = avatarModel->findParabolaIntersectionAgainstSubMeshes(pick.origin, pick.velocity, pick.acceleration,
parabolicDistance, face, surfaceNormal, extraInfo, true);
if (intersects && (!result.intersects || parabolicDistance < result.parabolicDistance)) {
result.intersects = true;
result.avatarID = avatar->getID();
result.parabolicDistance = parabolicDistance;
result.face = face;
result.surfaceNormal = surfaceNormal;
result.extraInfo = extraInfo;
}
}
if (result.intersects) {
result.intersection = pick.origin + pick.velocity * result.parabolicDistance + 0.5f * pick.acceleration * result.parabolicDistance * result.parabolicDistance;
result.distance = glm::distance(pick.origin, result.intersection);
}
return result;
}
// HACK
float AvatarManager::getAvatarSortCoefficient(const QString& name) {
if (name == "size") {
@ -668,3 +744,49 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV
DependencyManager::get<NodeList>()->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer);
}
}
QVariantMap AvatarManager::getPalData(const QList<QString> specificAvatarIdentifiers) {
QJsonArray palData;
auto avatarMap = getHashCopy();
AvatarHash::iterator itr = avatarMap.begin();
while (itr != avatarMap.end()) {
std::shared_ptr<Avatar> avatar = std::static_pointer_cast<Avatar>(*itr);
QString currentSessionUUID = avatar->getSessionUUID().toString();
if (specificAvatarIdentifiers.isEmpty() || specificAvatarIdentifiers.contains(currentSessionUUID)) {
QJsonObject thisAvatarPalData;
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
if (currentSessionUUID == myAvatar->getSessionUUID().toString()) {
currentSessionUUID = "";
}
thisAvatarPalData.insert("sessionUUID", currentSessionUUID);
thisAvatarPalData.insert("sessionDisplayName", avatar->getSessionDisplayName());
thisAvatarPalData.insert("audioLoudness", avatar->getAudioLoudness());
thisAvatarPalData.insert("isReplicated", avatar->getIsReplicated());
glm::vec3 position = avatar->getWorldPosition();
QJsonObject jsonPosition;
jsonPosition.insert("x", position.x);
jsonPosition.insert("y", position.y);
jsonPosition.insert("z", position.z);
thisAvatarPalData.insert("position", jsonPosition);
float palOrbOffset = 0.2f;
int headIndex = avatar->getJointIndex("Head");
if (headIndex > 0) {
glm::vec3 jointTranslation = avatar->getAbsoluteJointTranslationInObjectFrame(headIndex);
palOrbOffset = jointTranslation.y / 2;
}
thisAvatarPalData.insert("palOrbOffset", palOrbOffset);
palData.append(thisAvatarPalData);
}
++itr;
}
QJsonObject doc;
doc.insert("data", palData);
return doc.toVariantMap();
}

View file

@ -142,6 +142,10 @@ public:
const QVector<EntityItemID>& avatarsToInclude,
const QVector<EntityItemID>& avatarsToDiscard);
Q_INVOKABLE ParabolaToAvatarIntersectionResult findParabolaIntersectionVector(const PickParabola& pick,
const QVector<EntityItemID>& avatarsToInclude,
const QVector<EntityItemID>& avatarsToDiscard);
/**jsdoc
* @function AvatarManager.getAvatarSortCoefficient
* @param {string} name
@ -157,6 +161,17 @@ public:
*/
Q_INVOKABLE void setAvatarSortCoefficient(const QString& name, const QScriptValue& value);
/**jsdoc
* Used in the PAL for getting PAL-related data about avatars nearby. Using this method is faster
* than iterating over each avatar and obtaining data about them in JavaScript, as that method
* locks and unlocks each avatar's data structure potentially hundreds of times per update tick.
* @function AvatarManager.getPalData
* @param {string[]} specificAvatarIdentifiers - A list of specific Avatar Identifiers about
* which you want to get PAL data
* @returns {object}
*/
Q_INVOKABLE QVariantMap getPalData(const QList<QString> specificAvatarIdentifiers = QList<QString>());
float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); }
int getIdentityRequestsSent() const { return _identityRequestsSent; }

View file

@ -262,6 +262,26 @@ void MyAvatar::setDominantHand(const QString& hand) {
}
}
void MyAvatar::requestDisableHandTouch() {
std::lock_guard<std::mutex> guard(_disableHandTouchMutex);
_disableHandTouchCount++;
emit shouldDisableHandTouchChanged(_disableHandTouchCount > 0);
}
void MyAvatar::requestEnableHandTouch() {
std::lock_guard<std::mutex> guard(_disableHandTouchMutex);
_disableHandTouchCount = std::max(_disableHandTouchCount - 1, 0);
emit shouldDisableHandTouchChanged(_disableHandTouchCount > 0);
}
void MyAvatar::disableHandTouchForID(const QUuid& entityID) {
emit disableHandTouchForIDChanged(entityID, true);
}
void MyAvatar::enableHandTouchForID(const QUuid& entityID) {
emit disableHandTouchForIDChanged(entityID, false);
}
void MyAvatar::registerMetaTypes(ScriptEnginePointer engine) {
QScriptValue value = engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
engine->globalObject().setProperty("MyAvatar", value);
@ -557,9 +577,11 @@ void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool ca
void MyAvatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("simulate");
animateScaleChanges(deltaTime);
setFlyingEnabled(getFlyingEnabled());
if (_cauterizationNeedsUpdate) {
_cauterizationNeedsUpdate = false;
@ -704,16 +726,18 @@ void MyAvatar::simulate(float deltaTime) {
properties.setQueryAACubeDirty();
properties.setLastEdited(now);
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties);
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree,
entity->getID(), properties);
entity->setLastBroadcast(usecTimestampNow());
entity->forEachDescendant([&](SpatiallyNestablePointer descendant) {
EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant);
if (!entityDescendant->getClientOnly() && descendant->updateQueryAACube()) {
EntityItemPointer entityDescendant = std::dynamic_pointer_cast<EntityItem>(descendant);
if (entityDescendant && !entityDescendant->getClientOnly() && descendant->updateQueryAACube()) {
EntityItemProperties descendantProperties;
descendantProperties.setQueryAACube(descendant->getQueryAACube());
descendantProperties.setLastEdited(now);
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entityDescendant->getID(), descendantProperties);
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree,
entityDescendant->getID(), descendantProperties);
entityDescendant->setLastBroadcast(now); // for debug/physics status icons
}
});
@ -1111,7 +1135,8 @@ void MyAvatar::saveData() {
settings.setValue("collisionSoundURL", _collisionSoundURL);
settings.setValue("useSnapTurn", _useSnapTurn);
settings.setValue("userHeight", getUserHeight());
settings.setValue("enabledFlying", getFlyingEnabled());
settings.setValue("flyingDesktop", getFlyingDesktopPref());
settings.setValue("flyingHMD", getFlyingHMDPref());
settings.endGroup();
}
@ -1261,7 +1286,13 @@ void MyAvatar::loadData() {
settings.remove("avatarEntityData");
}
setAvatarEntityDataChanged(true);
setFlyingEnabled(settings.value("enabledFlying").toBool());
// Flying preferences must be loaded before calling setFlyingEnabled()
Setting::Handle<bool> firstRunVal { Settings::firstRun, true };
setFlyingDesktopPref(firstRunVal.get() ? true : settings.value("flyingDesktop").toBool());
setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool());
setFlyingEnabled(getFlyingEnabled());
setDisplayName(settings.value("displayName").toString());
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());
@ -1587,14 +1618,16 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
emit skeletonModelURLChanged();
}
void MyAvatar::removeAvatarEntities() {
void MyAvatar::removeAvatarEntities(const std::function<bool(const QUuid& entityID)>& condition) {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (entityTree) {
entityTree->withWriteLock([&] {
AvatarEntityMap avatarEntities = getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
entityTree->deleteEntity(entityID, true, true);
if (!condition || condition(entityID)) {
entityTree->deleteEntity(entityID, true, true);
}
}
});
}
@ -2028,6 +2061,8 @@ void MyAvatar::initAnimGraph() {
graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json");
}
emit animGraphUrlChanged(graphUrl);
_skeletonModel->getRig().initAnimGraph(graphUrl);
_currentAnimGraphUrl.set(graphUrl);
connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded()));
@ -2802,6 +2837,12 @@ void MyAvatar::setFlyingEnabled(bool enabled) {
return;
}
if (qApp->isHMDMode()) {
setFlyingHMDPref(enabled);
} else {
setFlyingDesktopPref(enabled);
}
_enableFlying = enabled;
}
@ -2817,7 +2858,33 @@ bool MyAvatar::isInAir() {
bool MyAvatar::getFlyingEnabled() {
// May return true even if client is not allowed to fly in the zone.
return _enableFlying;
return (qApp->isHMDMode() ? getFlyingHMDPref() : getFlyingDesktopPref());
}
void MyAvatar::setFlyingDesktopPref(bool enabled) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setFlyingDesktopPref", Q_ARG(bool, enabled));
return;
}
_flyingPrefDesktop = enabled;
}
bool MyAvatar::getFlyingDesktopPref() {
return _flyingPrefDesktop;
}
void MyAvatar::setFlyingHMDPref(bool enabled) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setFlyingHMDPref", Q_ARG(bool, enabled));
return;
}
_flyingPrefHMD = enabled;
}
bool MyAvatar::getFlyingHMDPref() {
return _flyingPrefHMD;
}
// Public interface for targetscale
@ -3019,7 +3086,7 @@ static glm::vec3 dampenCgMovement(glm::vec3 cgUnderHeadHandsAvatarSpace, float b
}
// computeCounterBalance returns the center of gravity in Avatar space
glm::vec3 MyAvatar::computeCounterBalance() const {
glm::vec3 MyAvatar::computeCounterBalance() {
struct JointMass {
QString name;
float weight;
@ -3037,7 +3104,8 @@ glm::vec3 MyAvatar::computeCounterBalance() const {
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);
glm::vec3 tposeHips = DEFAULT_AVATAR_HIPS_POS;
glm::vec3 tposeRightFoot = DEFAULT_AVATAR_RIGHTFOOT_POS;
if (_skeletonModel->getRig().indexOfJoint(cgHeadMass.name) != -1) {
cgHeadMass.position = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint(cgHeadMass.name));
@ -3056,6 +3124,9 @@ glm::vec3 MyAvatar::computeCounterBalance() const {
if (_skeletonModel->getRig().indexOfJoint("Hips") != -1) {
tposeHips = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("Hips"));
}
if (_skeletonModel->getRig().indexOfJoint("RightFoot") != -1) {
tposeRightFoot = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("RightFoot"));
}
// 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);
@ -3076,9 +3147,12 @@ glm::vec3 MyAvatar::computeCounterBalance() const {
glm::vec3 counterBalancedCg = (1.0f / DEFAULT_AVATAR_HIPS_MASS) * counterBalancedForHead;
// find the height of the hips
const float UPPER_LEG_FRACTION = 0.3333f;
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 hipFootDefault = tposeHips.y - tposeRightFoot.y;
float sitSquatThreshold = tposeHips.y - (UPPER_LEG_FRACTION * hipFootDefault);
float hipHeight = 0.0f;
if (headHipDefault > headMinusHipXz) {
hipHeight = sqrtf((headHipDefault * headHipDefault) - (headMinusHipXz * headMinusHipXz));
@ -3090,6 +3164,10 @@ glm::vec3 MyAvatar::computeCounterBalance() const {
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;
} else if (counterBalancedCg.y < sitSquatThreshold) {
//do a height reset
setResetMode(true);
_follow.activate(FollowHelper::Vertical);
}
return counterBalancedCg;
}
@ -3139,7 +3217,7 @@ static void drawBaseOfSupport(float baseOfSupportScale, float footLocal, glm::ma
// 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 MyAvatar::deriveBodyUsingCgModel() {
glm::mat4 sensorToWorldMat = getSensorToWorldMatrix();
glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat);
auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
@ -3157,7 +3235,7 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() const {
}
// get the new center of gravity
const glm::vec3 cgHipsPosition = computeCounterBalance();
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);
@ -3551,6 +3629,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) {
if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Rotation);
myAvatar.setHeadControllerFacingMovingAverage(myAvatar._headControllerFacing);
}
if (myAvatar.getCenterOfGravityModelEnabled()) {
if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) {
@ -3568,6 +3647,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
} else {
if (!isActive(Rotation) && getForceActivateRotation()) {
activate(Rotation);
myAvatar.setHeadControllerFacingMovingAverage(myAvatar._headControllerFacing);
setForceActivateRotation(false);
}
if (!isActive(Horizontal) && getForceActivateHorizontal()) {

View file

@ -33,6 +33,7 @@
#include "MyCharacterController.h"
#include "RingBufferHistory.h"
#include <ThreadSafeValueCache.h>
#include <EntityItem.h>
class AvatarActionHold;
class ModelItemID;
@ -505,6 +506,28 @@ public:
* @returns {boolean}
*/
Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; }
/**jsdoc
* Request to enable hand touch effect globally
* @function MyAvatar.requestEnableHandTouch
*/
Q_INVOKABLE void requestEnableHandTouch();
/**jsdoc
* Request to disable hand touch effect globally
* @function MyAvatar.requestDisableHandTouch
*/
Q_INVOKABLE void requestDisableHandTouch();
/**jsdoc
* Disables hand touch effect on a specific entity
* @function MyAvatar.disableHandTouchForID
* @param {Uuid} entityID - ID of the entity that will disable hand touch effect
*/
Q_INVOKABLE void disableHandTouchForID(const QUuid& entityID);
/**jsdoc
* Enables hand touch effect on a specific entity
* @function MyAvatar.enableHandTouchForID
* @param {Uuid} entityID - ID of the entity that will enable hand touch effect
*/
Q_INVOKABLE void enableHandTouchForID(const QUuid& entityID);
bool useAdvancedMovementControls() const { return _useAdvancedMovementControls.get(); }
void setUseAdvancedMovementControls(bool useAdvancedMovementControls)
@ -590,6 +613,8 @@ public:
const MyHead* getMyHead() const;
Q_INVOKABLE void toggleSmoothPoleVectors() { _skeletonModel->getRig().toggleSmoothPoleVectors(); };
/**jsdoc
* Get the current position of the avatar's "Head" joint.
* @function MyAvatar.getHeadPosition
@ -883,6 +908,7 @@ public:
virtual void rebuildCollisionShape() override;
const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; }
void setHeadControllerFacingMovingAverage(glm::vec2 currentHeadControllerFacing) { _headControllerFacingMovingAverage = currentHeadControllerFacing; }
float getCurrentStandingHeight() const { return _currentStandingHeight; }
void setCurrentStandingHeight(float newMode) { _currentStandingHeight = newMode; }
const glm::quat getAverageHeadRotation() const { return _averageHeadRotation; }
@ -903,7 +929,7 @@ public:
* @returns {object[]}
*/
Q_INVOKABLE QVariantList getAvatarEntitiesVariant();
void removeAvatarEntities();
void removeAvatarEntities(const std::function<bool(const QUuid& entityID)>& condition = {});
/**jsdoc
* @function MyAvatar.isFlying
@ -929,6 +955,30 @@ public:
*/
Q_INVOKABLE bool getFlyingEnabled();
/**jsdoc
* @function MyAvatar.setFlyingDesktopPref
* @param {boolean} enabled
*/
Q_INVOKABLE void setFlyingDesktopPref(bool enabled);
/**jsdoc
* @function MyAvatar.getFlyingDesktopPref
* @returns {boolean}
*/
Q_INVOKABLE bool getFlyingDesktopPref();
/**jsdoc
* @function MyAvatar.setFlyingDesktopPref
* @param {boolean} enabled
*/
Q_INVOKABLE void setFlyingHMDPref(bool enabled);
/**jsdoc
* @function MyAvatar.getFlyingDesktopPref
* @returns {boolean}
*/
Q_INVOKABLE bool getFlyingHMDPref();
/**jsdoc
* @function MyAvatar.getAvatarScale
@ -993,12 +1043,12 @@ public:
// results are in sensor frame (-z forward)
glm::mat4 deriveBodyFromHMDSensor() const;
glm::vec3 computeCounterBalance() const;
glm::vec3 computeCounterBalance();
// 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;
glm::mat4 deriveBodyUsingCgModel();
/**jsdoc
* @function MyAvatar.isUp
@ -1391,6 +1441,23 @@ signals:
*/
void scaleChanged();
/**jsdoc
* Triggered when hand touch is globally enabled or disabled
* @function MyAvatar.shouldDisableHandTouchChanged
* @param {boolean} shouldDisable
* @returns {Signal}
*/
void shouldDisableHandTouchChanged(bool shouldDisable);
/**jsdoc
* Triggered when hand touch is enabled or disabled for an specific entity
* @function MyAvatar.disableHandTouchForIDChanged
* @param {Uuid} entityID - ID of the entity that will enable hand touch effect
* @param {boolean} disable
* @returns {Signal}
*/
void disableHandTouchForIDChanged(const QUuid& entityID, bool disable);
private slots:
void leaveDomain();
@ -1462,6 +1529,8 @@ private:
std::bitset<MAX_DRIVE_KEYS> _disabledDriveKeys;
bool _enableFlying { false };
bool _flyingPrefDesktop { true };
bool _flyingPrefHMD { false };
bool _wasPushing { false };
bool _isPushing { false };
bool _isBeingPushed { false };
@ -1627,6 +1696,7 @@ private:
// all poses are in sensor-frame
std::map<controller::Action, controller::Pose> _controllerPoseMap;
mutable std::mutex _controllerPoseMapMutex;
mutable std::mutex _disableHandTouchMutex;
bool _centerOfGravityModelEnabled { true };
bool _hmdLeanRecenterEnabled { true };
@ -1667,6 +1737,7 @@ private:
bool _shouldLoadScripts { false };
bool _haveReceivedHeightLimitsFromDomain { false };
int _disableHandTouchCount { 0 };
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

@ -48,7 +48,7 @@ void OtherAvatar::createOrb() {
_otherAvatarOrbMeshPlaceholder->setPulseMin(0.5);
_otherAvatarOrbMeshPlaceholder->setPulseMax(1.0);
_otherAvatarOrbMeshPlaceholder->setColorPulse(1.0);
_otherAvatarOrbMeshPlaceholder->setIgnoreRayIntersection(true);
_otherAvatarOrbMeshPlaceholder->setIgnorePickIntersection(true);
_otherAvatarOrbMeshPlaceholder->setDrawInFront(false);
_otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder);
// Position focus

View file

@ -262,6 +262,9 @@ int main(int argc, const char* argv[]) {
// Extend argv to enable WebGL rendering
std::vector<const char*> argvExtended(&argv[0], &argv[argc]);
argvExtended.push_back("--ignore-gpu-blacklist");
#ifdef Q_OS_ANDROID
argvExtended.push_back("--suppress-settings-reset");
#endif
int argcExtended = (int)argvExtended.size();
PROFILE_SYNC_END(startup, "main startup", "");

View file

@ -0,0 +1,43 @@
//
// Created by Sam Gondelman 7/2/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
//
#include "JointParabolaPick.h"
#include "avatar/AvatarManager.h"
JointParabolaPick::JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset,
float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, PickFilter& filter, float maxDistance, bool enabled) :
ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled),
_jointName(jointName),
_posOffset(posOffset),
_dirOffset(dirOffset)
{
}
PickParabola JointParabolaPick::getMathematicalPick() const {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
int jointIndex = myAvatar->getJointIndex(QString::fromStdString(_jointName));
bool useAvatarHead = _jointName == "Avatar";
const int INVALID_JOINT = -1;
if (jointIndex != INVALID_JOINT || useAvatarHead) {
glm::vec3 jointPos = useAvatarHead ? myAvatar->getHeadPosition() : myAvatar->getAbsoluteJointTranslationInObjectFrame(jointIndex);
glm::quat jointRot = useAvatarHead ? myAvatar->getHeadOrientation() : myAvatar->getAbsoluteJointRotationInObjectFrame(jointIndex);
glm::vec3 avatarPos = myAvatar->getWorldPosition();
glm::quat avatarRot = myAvatar->getWorldOrientation();
glm::vec3 pos = useAvatarHead ? jointPos : avatarPos + (avatarRot * jointPos);
glm::quat rot = useAvatarHead ? jointRot * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT) : avatarRot * jointRot;
// Apply offset
pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset));
glm::vec3 dir = glm::normalize(rot * glm::normalize(_dirOffset));
return PickParabola(pos, getSpeed() * dir, getAcceleration());
}
return PickParabola();
}

View file

@ -0,0 +1,32 @@
//
// Created by Sam Gondelman 7/2/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
//
#ifndef hifi_JointParabolaPick_h
#define hifi_JointParabolaPick_h
#include "ParabolaPick.h"
class JointParabolaPick : public ParabolaPick {
public:
JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset,
float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar,
PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
PickParabola getMathematicalPick() const override;
bool isLeftHand() const override { return (_jointName == "_CONTROLLER_LEFTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); }
bool isRightHand() const override { return (_jointName == "_CONTROLLER_RIGHTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"); }
private:
std::string _jointName;
glm::vec3 _posOffset;
glm::vec3 _dirOffset;
};
#endif // hifi_JointParabolaPick_h

View file

@ -36,7 +36,7 @@ PickRay JointRayPick::getMathematicalPick() const {
// Apply offset
pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset));
glm::vec3 dir = rot * glm::normalize(_dirOffset);
glm::vec3 dir = glm::normalize(rot * glm::normalize(_dirOffset));
return PickRay(pos, dir);
}

View file

@ -14,315 +14,114 @@
#include "avatar/AvatarManager.h"
#include <DependencyManager.h>
#include <PickManager.h>
#include "PickScriptingInterface.h"
#include "RayPick.h"
LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover,
const PointerTriggers& triggers, bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) :
Pointer(DependencyManager::get<PickScriptingInterface>()->createRayPick(rayProps), enabled, hover),
_triggers(triggers),
_renderStates(renderStates),
_defaultRenderStates(defaultRenderStates),
_faceAvatar(faceAvatar),
_centerEndY(centerEndY),
_lockEnd(lockEnd),
_distanceScaleEnd(distanceScaleEnd),
_scaleWithAvatar(scaleWithAvatar)
const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalTime, bool centerEndY, bool lockEnd,
bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) :
PathPointer(PickQuery::Ray, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalTime,
centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled)
{
for (auto& state : _renderStates) {
if (!enabled || state.first != _currentRenderState) {
disableRenderState(state.second);
}
}
for (auto& state : _defaultRenderStates) {
if (!enabled || state.first != _currentRenderState) {
disableRenderState(state.second.second);
}
}
}
LaserPointer::~LaserPointer() {
for (auto& renderState : _renderStates) {
renderState.second.deleteOverlays();
}
for (auto& renderState : _defaultRenderStates) {
renderState.second.second.deleteOverlays();
}
}
void LaserPointer::setRenderState(const std::string& state) {
withWriteLock([&] {
if (!_currentRenderState.empty() && state != _currentRenderState) {
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]);
}
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_defaultRenderStates[_currentRenderState].second);
}
}
_currentRenderState = state;
});
}
void LaserPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) {
withWriteLock([&] {
updateRenderStateOverlay(_renderStates[state].getStartID(), startProps);
updateRenderStateOverlay(_renderStates[state].getPathID(), pathProps);
updateRenderStateOverlay(_renderStates[state].getEndID(), endProps);
QVariant endDim = endProps.toMap()["dimensions"];
if (endDim.isValid()) {
_renderStates[state].setEndDim(vec3FromVariant(endDim));
}
void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) {
auto renderState = std::static_pointer_cast<RenderState>(_renderStates[state]);
if (renderState) {
updateRenderStateOverlay(renderState->getPathID(), pathProps);
QVariant lineWidth = pathProps.toMap()["lineWidth"];
if (lineWidth.isValid()) {
_renderStates[state].setLineWidth(lineWidth.toFloat());
}
});
}
PickResultPointer LaserPointer::getVisualPickResult(const PickResultPointer& pickResult) {
PickResultPointer visualPickResult = pickResult;
auto rayPickResult = std::static_pointer_cast<RayPickResult>(visualPickResult);
IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE;
if (type != IntersectionType::HUD) {
glm::vec3 endVec;
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay();
if (!_lockEndObject.id.isNull()) {
glm::vec3 pos;
glm::quat rot;
glm::vec3 dim;
glm::vec3 registrationPoint;
if (_lockEndObject.isOverlay) {
pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value);
rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value);
dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value);
registrationPoint = glm::vec3(0.5f);
} else {
EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_lockEndObject.id);
glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition());
glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat;
pos = extractTranslation(finalPosAndRotMat);
rot = glmExtractRotation(finalPosAndRotMat);
dim = props.getDimensions();
registrationPoint = props.getRegistrationPoint();
}
const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);
endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint));
glm::vec3 direction = endVec - pickRay.origin;
float distance = glm::distance(pickRay.origin, endVec);
glm::vec3 normalizedDirection = glm::normalize(direction);
rayPickResult->type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY;
rayPickResult->objectID = _lockEndObject.id;
rayPickResult->intersection = endVec;
rayPickResult->distance = distance;
rayPickResult->surfaceNormal = -normalizedDirection;
rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection);
} else if (type != IntersectionType::NONE && _lockEnd) {
if (type == IntersectionType::ENTITY) {
endVec = DependencyManager::get<EntityScriptingInterface>()->getEntityTransform(rayPickResult->objectID)[3];
} else if (type == IntersectionType::OVERLAY) {
endVec = vec3FromVariant(qApp->getOverlays().getProperty(rayPickResult->objectID, "position").value);
} else if (type == IntersectionType::AVATAR) {
endVec = DependencyManager::get<AvatarHashMap>()->getAvatar(rayPickResult->objectID)->getPosition();
}
glm::vec3 direction = endVec - pickRay.origin;
float distance = glm::distance(pickRay.origin, endVec);
glm::vec3 normalizedDirection = glm::normalize(direction);
rayPickResult->intersection = endVec;
rayPickResult->distance = distance;
rayPickResult->surfaceNormal = -normalizedDirection;
rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection);
renderState->setLineWidth(lineWidth.toFloat());
}
}
return visualPickResult;
}
void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
if (!id.isNull() && props.isValid()) {
QVariantMap propMap = props.toMap();
propMap.remove("visible");
qApp->getOverlays().editOverlay(id, propMap);
glm::vec3 LaserPointer::getPickOrigin(const PickResultPointer& pickResult) const {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
return (rayPickResult ? vec3FromVariant(rayPickResult->pickVariant["origin"]) : glm::vec3(0.0f));
}
glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
if (distance > 0.0f) {
PickRay pick = PickRay(rayPickResult->pickVariant);
return pick.origin + distance * pick.direction;
} else {
return rayPickResult->intersection;
}
}
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay) {
if (!renderState.getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("position", vec3toVariant(pickRay.origin));
startProps.insert("visible", true);
startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays());
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
}
glm::vec3 endVec = pickRay.origin + pickRay.direction * distance;
QVariant end = vec3toVariant(endVec);
if (!renderState.getPathID().isNull()) {
QVariantMap pathProps;
pathProps.insert("start", vec3toVariant(pickRay.origin));
pathProps.insert("end", end);
pathProps.insert("visible", true);
pathProps.insert("ignoreRayIntersection", renderState.doesPathIgnoreRays());
if (_scaleWithAvatar) {
pathProps.insert("lineWidth", renderState.getLineWidth() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale());
}
qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps);
}
if (!renderState.getEndID().isNull()) {
QVariantMap endProps;
glm::quat faceAvatarRotation = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f)));
glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(renderState.getEndID(), "dimensions").value);
if (_distanceScaleEnd) {
dim = renderState.getEndDim() * glm::distance(pickRay.origin, endVec);
endProps.insert("dimensions", vec3toVariant(dim));
}
if (_centerEndY) {
endProps.insert("position", end);
} else {
glm::vec3 currentUpVector = faceAvatarRotation * Vectors::UP;
endProps.insert("position", vec3toVariant(endVec + glm::vec3(currentUpVector.x * 0.5f * dim.y, currentUpVector.y * 0.5f * dim.y, currentUpVector.z * 0.5f * dim.y)));
}
if (_faceAvatar) {
endProps.insert("rotation", quatToVariant(faceAvatarRotation));
}
endProps.insert("visible", true);
endProps.insert("ignoreRayIntersection", renderState.doesEndIgnoreRays());
qApp->getOverlays().editOverlay(renderState.getEndID(), endProps);
}
glm::vec3 LaserPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
return (rayPickResult ? rayPickResult->surfaceNormal : glm::vec3(0.0f));
}
void LaserPointer::disableRenderState(const RenderState& renderState) {
if (!renderState.getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("visible", false);
startProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
}
if (!renderState.getPathID().isNull()) {
QVariantMap pathProps;
pathProps.insert("visible", false);
pathProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps);
}
if (!renderState.getEndID().isNull()) {
QVariantMap endProps;
endProps.insert("visible", false);
endProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(renderState.getEndID(), endProps);
}
IntersectionType LaserPointer::getPickedObjectType(const PickResultPointer& pickResult) const {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
return (rayPickResult ? rayPickResult->type : IntersectionType::NONE);
}
void LaserPointer::updateVisuals(const PickResultPointer& pickResult) {
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE;
if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
(type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) {
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant): PickRay();
QUuid uid = rayPickResult->objectID;
float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance;
updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
} else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]);
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay();
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay);
} else if (!_currentRenderState.empty()) {
disableRenderState(_renderStates[_currentRenderState]);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
}
QUuid LaserPointer::getPickedObjectID(const PickResultPointer& pickResult) const {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
return (rayPickResult ? rayPickResult->objectID : QUuid());
}
Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pickResult) {
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
if (!rayPickResult) {
return PickedObject();
}
return PickedObject(rayPickResult->objectID, rayPickResult->type);
}
Pointer::Buttons LaserPointer::getPressedButtons(const PickResultPointer& pickResult) {
std::unordered_set<std::string> toReturn;
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
void LaserPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
if (rayPickResult) {
for (const PointerTrigger& trigger : _triggers) {
std::string button = trigger.getButton();
TriggerState& state = _states[button];
// TODO: right now, LaserPointers don't support axes, only on/off buttons
if (trigger.getEndpoint()->peek() >= 1.0f) {
toReturn.insert(button);
if (_previousButtons.find(button) == _previousButtons.end()) {
// start triggering for buttons that were just pressed
state.triggeredObject = PickedObject(rayPickResult->objectID, rayPickResult->type);
state.intersection = rayPickResult->intersection;
state.triggerPos2D = findPos2D(state.triggeredObject, rayPickResult->intersection);
state.triggerStartTime = usecTimestampNow();
state.surfaceNormal = rayPickResult->surfaceNormal;
state.deadspotExpired = false;
state.wasTriggering = true;
state.triggering = true;
_latestState = state;
}
} else {
// stop triggering for buttons that aren't pressed
state.wasTriggering = state.triggering;
state.triggering = false;
_latestState = state;
}
}
_previousButtons = toReturn;
rayPickResult->type = type;
rayPickResult->objectID = id;
rayPickResult->intersection = intersection;
rayPickResult->distance = distance;
rayPickResult->surfaceNormal = surfaceNormal;
rayPickResult->pickVariant["direction"] = vec3toVariant(-surfaceNormal);
}
return toReturn;
}
void LaserPointer::setLength(float length) {
withWriteLock([&] {
_laserLength = length;
});
}
void LaserPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) {
withWriteLock([&] {
_lockEndObject.id = objectID;
_lockEndObject.isOverlay = isOverlay;
_lockEndObject.offsetMat = offsetMat;
});
}
RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) :
_startID(startID), _pathID(pathID), _endID(endID)
LaserPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) :
StartEndRenderState(startID, endID), _pathID(pathID)
{
if (!_startID.isNull()) {
_startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool();
}
if (!_pathID.isNull()) {
_pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignoreRayIntersection").value.toBool();
_lineWidth = qApp->getOverlays().getProperty(_pathID, "lineWidth").value.toFloat();
}
if (!_endID.isNull()) {
_endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value);
_endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool();
}
}
void RenderState::deleteOverlays() {
if (!_startID.isNull()) {
qApp->getOverlays().deleteOverlay(_startID);
}
void LaserPointer::RenderState::cleanup() {
StartEndRenderState::cleanup();
if (!_pathID.isNull()) {
qApp->getOverlays().deleteOverlay(_pathID);
}
if (!_endID.isNull()) {
qApp->getOverlays().deleteOverlay(_endID);
}
void LaserPointer::RenderState::disable() {
StartEndRenderState::disable();
if (!getPathID().isNull()) {
QVariantMap pathProps;
pathProps.insert("visible", false);
pathProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(getPathID(), pathProps);
}
}
RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) {
StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult);
QVariant endVariant = vec3toVariant(end);
if (!getPathID().isNull()) {
QVariantMap pathProps;
pathProps.insert("start", vec3toVariant(origin));
pathProps.insert("end", endVariant);
pathProps.insert("visible", true);
pathProps.insert("ignoreRayIntersection", doesPathIgnoreRays());
if (scaleWithAvatar) {
pathProps.insert("lineWidth", getLineWidth() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale());
}
qApp->getOverlays().editOverlay(getPathID(), pathProps);
}
}
std::shared_ptr<StartEndRenderState> LaserPointer::buildRenderState(const QVariantMap& propMap) {
QUuid startID;
if (propMap["start"].isValid()) {
QVariantMap startMap = propMap["start"].toMap();
@ -335,7 +134,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
QUuid pathID;
if (propMap["path"].isValid()) {
QVariantMap pathMap = propMap["path"].toMap();
// right now paths must be line3ds
// laser paths must be line3ds
if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") {
pathMap.remove("visible");
pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap);
@ -351,7 +150,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
}
}
return RenderState(startID, pathID, endID);
return std::make_shared<RenderState>(startID, pathID, endID);
}
PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) {
@ -391,24 +190,11 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P
glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) {
switch (pickedObject.type) {
case ENTITY:
return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction);
case OVERLAY:
return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction);
default:
return glm::vec3(NAN);
}
}
glm::vec2 LaserPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) {
switch (pickedObject.type) {
case ENTITY:
return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin);
case OVERLAY:
return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin);
case HUD:
return DependencyManager::get<PickManager>()->calculatePos2DFromHUD(origin);
default:
return glm::vec2(NAN);
case ENTITY:
return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction);
case OVERLAY:
return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction);
default:
return glm::vec3(NAN);
}
}

View file

@ -11,117 +11,54 @@
#ifndef hifi_LaserPointer_h
#define hifi_LaserPointer_h
#include <QString>
#include <glm/glm.hpp>
#include "ui/overlays/Overlay.h"
#include <Pointer.h>
#include <Pick.h>
struct LockEndObject {
QUuid id { QUuid() };
bool isOverlay { false };
glm::mat4 offsetMat { glm::mat4() };
};
class RenderState {
#include "PathPointer.h"
class LaserPointer : public PathPointer {
using Parent = PathPointer;
public:
RenderState() {}
RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID);
class RenderState : public StartEndRenderState {
public:
RenderState() {}
RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID);
const OverlayID& getStartID() const { return _startID; }
const OverlayID& getPathID() const { return _pathID; }
const OverlayID& getEndID() const { return _endID; }
const bool& doesStartIgnoreRays() const { return _startIgnoreRays; }
const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; }
const bool& doesEndIgnoreRays() const { return _endIgnoreRays; }
const OverlayID& getPathID() const { return _pathID; }
const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; }
void setEndDim(const glm::vec3& endDim) { _endDim = endDim; }
const glm::vec3& getEndDim() const { return _endDim; }
void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; }
const float& getLineWidth() const { return _lineWidth; }
void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; }
const float& getLineWidth() const { return _lineWidth; }
void cleanup() override;
void disable() override;
void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override;
void deleteOverlays();
private:
OverlayID _pathID;
bool _pathIgnoreRays;
private:
OverlayID _startID;
OverlayID _pathID;
OverlayID _endID;
bool _startIgnoreRays;
bool _pathIgnoreRays;
bool _endIgnoreRays;
glm::vec3 _endDim;
float _lineWidth;
};
class LaserPointer : public Pointer {
using Parent = Pointer;
public:
typedef std::unordered_map<std::string, RenderState> RenderStateMap;
typedef std::unordered_map<std::string, std::pair<float, RenderState>> DefaultRenderStateMap;
LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers,
bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
~LaserPointer();
void setRenderState(const std::string& state) override;
// You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays.
void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override;
void setLength(float length) override;
void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override;
void updateVisuals(const PickResultPointer& prevRayPickResult) override;
static RenderState buildRenderState(const QVariantMap& propMap);
protected:
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override;
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override;
bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
private:
PointerTriggers _triggers;
float _laserLength { 0.0f };
std::string _currentRenderState { "" };
RenderStateMap _renderStates;
DefaultRenderStateMap _defaultRenderStates;
bool _faceAvatar;
bool _centerEndY;
bool _lockEnd;
bool _distanceScaleEnd;
bool _scaleWithAvatar;
LockEndObject _lockEndObject;
void updateRenderStateOverlay(const OverlayID& id, const QVariant& props);
void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay);
void disableRenderState(const RenderState& renderState);
struct TriggerState {
PickedObject triggeredObject;
glm::vec3 intersection { NAN };
glm::vec3 surfaceNormal { NAN };
glm::vec2 triggerPos2D { NAN };
quint64 triggerStartTime { 0 };
bool deadspotExpired { true };
bool triggering { false };
bool wasTriggering { false };
float _lineWidth;
};
Pointer::Buttons _previousButtons;
std::unordered_map<std::string, TriggerState> _states;
TriggerState _latestState;
LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers,
bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
static std::shared_ptr<StartEndRenderState> buildRenderState(const QVariantMap& propMap);
protected:
void editRenderStatePath(const std::string& state, const QVariant& pathProps) override;
glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override;
glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) const override;
glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const override;
IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const override;
QUuid getPickedObjectID(const PickResultPointer& pickResult) const override;
void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) override;
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
private:
static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction);
static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin);
};

View file

@ -0,0 +1,28 @@
//
// Created by Sam Gondelman 7/2/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
//
#include "MouseParabolaPick.h"
#include "Application.h"
#include "display-plugins/CompositorHelper.h"
MouseParabolaPick::MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar,
bool scaleWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) :
ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled)
{
}
PickParabola MouseParabolaPick::getMathematicalPick() const {
QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition();
if (position.isValid()) {
QVariantMap posMap = position.toMap();
PickRay pickRay = qApp->getCamera().computePickRay(posMap["x"].toFloat(), posMap["y"].toFloat());
return PickParabola(pickRay.origin, getSpeed() * pickRay.direction, getAcceleration());
}
return PickParabola();
}

View file

@ -0,0 +1,24 @@
//
// Created by Sam Gondelman 7/2/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
//
#ifndef hifi_MouseParabolaPick_h
#define hifi_MouseParabolaPick_h
#include "ParabolaPick.h"
class MouseParabolaPick : public ParabolaPick {
public:
MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar,
const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
PickParabola getMathematicalPick() const override;
bool isMouse() const override { return true; }
};
#endif // hifi_MouseParabolaPick_h

View file

@ -0,0 +1,70 @@
//
// Created by Sam Gondelman 7/2/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
//
#include "ParabolaPick.h"
#include "Application.h"
#include "EntityScriptingInterface.h"
#include "ui/overlays/Overlays.h"
#include "avatar/AvatarManager.h"
#include "scripting/HMDScriptingInterface.h"
#include "DependencyManager.h"
PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) {
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
ParabolaToEntityIntersectionResult entityRes =
DependencyManager::get<EntityScriptingInterface>()->findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(),
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
if (entityRes.intersects) {
return std::make_shared<ParabolaPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo);
}
}
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
}
PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) {
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
ParabolaToOverlayIntersectionResult overlayRes =
qApp->getOverlays().findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(),
getIncludeItemsAs<OverlayID>(), getIgnoreItemsAs<OverlayID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
if (overlayRes.intersects) {
return std::make_shared<ParabolaPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.parabolicDistance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo);
}
}
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
}
PickResultPointer ParabolaPick::getAvatarIntersection(const PickParabola& pick) {
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
ParabolaToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findParabolaIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
if (avatarRes.intersects) {
return std::make_shared<ParabolaPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.parabolicDistance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo);
}
}
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
}
PickResultPointer ParabolaPick::getHUDIntersection(const PickParabola& pick) {
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
float parabolicDistance;
glm::vec3 hudRes = DependencyManager::get<HMDScriptingInterface>()->calculateParabolaUICollisionPoint(pick.origin, pick.velocity, pick.acceleration, parabolicDistance);
return std::make_shared<ParabolaPickResult>(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), parabolicDistance, hudRes, pick);
}
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
}
float ParabolaPick::getSpeed() const {
return (_scaleWithAvatar ? DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale() * _speed : _speed);
}
glm::vec3 ParabolaPick::getAcceleration() const {
float scale = (_scaleWithAvatar ? DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale() : 1.0f);
if (_rotateAccelerationWithAvatar) {
return scale * (DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * _accelerationAxis);
}
return scale * _accelerationAxis;
}

View file

@ -0,0 +1,97 @@
//
// Created by Sam Gondelman 7/2/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
//
#ifndef hifi_ParabolaPick_h
#define hifi_ParabolaPick_h
#include <RegisteredMetaTypes.h>
#include <Pick.h>
class EntityItemID;
class OverlayID;
class ParabolaPickResult : public PickResult {
public:
ParabolaPickResult() {}
ParabolaPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
ParabolaPickResult(const IntersectionType type, const QUuid& objectID, float distance, float parabolicDistance, const glm::vec3& intersection, const PickParabola& parabola,
const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) :
PickResult(parabola.toVariantMap()), extraInfo(extraInfo), objectID(objectID), intersection(intersection), surfaceNormal(surfaceNormal), type(type), distance(distance), parabolicDistance(parabolicDistance), intersects(type != NONE) {
}
ParabolaPickResult(const ParabolaPickResult& parabolaPickResult) : PickResult(parabolaPickResult.pickVariant) {
type = parabolaPickResult.type;
intersects = parabolaPickResult.intersects;
objectID = parabolaPickResult.objectID;
distance = parabolaPickResult.distance;
parabolicDistance = parabolaPickResult.parabolicDistance;
intersection = parabolaPickResult.intersection;
surfaceNormal = parabolaPickResult.surfaceNormal;
extraInfo = parabolaPickResult.extraInfo;
}
QVariantMap extraInfo;
QUuid objectID;
glm::vec3 intersection { NAN };
glm::vec3 surfaceNormal { NAN };
IntersectionType type { NONE };
float distance { FLT_MAX };
float parabolicDistance { FLT_MAX };
bool intersects { false };
virtual QVariantMap toVariantMap() const override {
QVariantMap toReturn;
toReturn["type"] = type;
toReturn["intersects"] = intersects;
toReturn["objectID"] = objectID;
toReturn["distance"] = distance;
toReturn["parabolicDistance"] = parabolicDistance;
toReturn["intersection"] = vec3toVariant(intersection);
toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal);
toReturn["parabola"] = PickResult::toVariantMap();
toReturn["extraInfo"] = extraInfo;
return toReturn;
}
bool doesIntersect() const override { return intersects; }
bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return parabolicDistance < maxDistance; }
PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override {
auto newParabolaRes = std::static_pointer_cast<ParabolaPickResult>(newRes);
if (newParabolaRes->parabolicDistance < parabolicDistance) {
return std::make_shared<ParabolaPickResult>(*newParabolaRes);
} else {
return std::make_shared<ParabolaPickResult>(*this);
}
}
};
class ParabolaPick : public Pick<PickParabola> {
public:
ParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) :
Pick(filter, maxDistance, enabled), _speed(speed), _accelerationAxis(accelerationAxis), _rotateAccelerationWithAvatar(rotateAccelerationWithAvatar),
_scaleWithAvatar(scaleWithAvatar) {}
PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared<ParabolaPickResult>(pickVariant); }
PickResultPointer getEntityIntersection(const PickParabola& pick) override;
PickResultPointer getOverlayIntersection(const PickParabola& pick) override;
PickResultPointer getAvatarIntersection(const PickParabola& pick) override;
PickResultPointer getHUDIntersection(const PickParabola& pick) override;
protected:
float _speed;
glm::vec3 _accelerationAxis;
bool _rotateAccelerationWithAvatar;
bool _scaleWithAvatar;
float getSpeed() const;
glm::vec3 getAcceleration() const;
};
#endif // hifi_ParabolaPick_h

View file

@ -0,0 +1,406 @@
//
// Created by Sam Gondelman 7/17/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
//
#include "ParabolaPointer.h"
#include "Application.h"
#include "avatar/AvatarManager.h"
#include <StencilMaskPass.h>
#include <DependencyManager.h>
#include "ParabolaPick.h"
#include "render-utils/parabola_vert.h"
#include "render-utils/parabola_frag.h"
const glm::vec4 ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR { 1.0f };
const float ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH { 0.01f };
const bool ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA { false };
gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_parabolaPipeline { nullptr };
gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_transparentParabolaPipeline { nullptr };
ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover,
const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd,
bool scaleWithAvatar, bool enabled) :
PathPointer(PickQuery::Parabola, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalStrength,
centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled)
{
}
void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) {
auto renderState = std::static_pointer_cast<RenderState>(_renderStates[state]);
if (renderState) {
QVariantMap pathMap = pathProps.toMap();
glm::vec3 color = glm::vec3(RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR);
float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a;
float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH;
bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA;
bool enabled = false;
if (!pathMap.isEmpty()) {
enabled = true;
if (pathMap["color"].isValid()) {
bool valid;
color = toGlm(xColorFromVariant(pathMap["color"], valid));
}
if (pathMap["alpha"].isValid()) {
alpha = pathMap["alpha"].toFloat();
}
if (pathMap["width"].isValid()) {
width = pathMap["width"].toFloat();
renderState->setPathWidth(width);
}
if (pathMap["isVisibleInSecondaryCamera"].isValid()) {
isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool();
}
}
renderState->editParabola(color, alpha, width, isVisibleInSecondaryCamera, enabled);
}
}
glm::vec3 ParabolaPointer::getPickOrigin(const PickResultPointer& pickResult) const {
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
return (parabolaPickResult ? vec3FromVariant(parabolaPickResult->pickVariant["origin"]) : glm::vec3(0.0f));
}
glm::vec3 ParabolaPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const {
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
if (distance > 0.0f) {
PickParabola pick = PickParabola(parabolaPickResult->pickVariant);
return pick.origin + pick.velocity * distance + 0.5f * pick.acceleration * distance * distance;
} else {
return parabolaPickResult->intersection;
}
}
glm::vec3 ParabolaPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const {
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
return (parabolaPickResult ? parabolaPickResult->surfaceNormal : glm::vec3(0.0f));
}
IntersectionType ParabolaPointer::getPickedObjectType(const PickResultPointer& pickResult) const {
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
return (parabolaPickResult ? parabolaPickResult->type : IntersectionType::NONE);
}
QUuid ParabolaPointer::getPickedObjectID(const PickResultPointer& pickResult) const {
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
return (parabolaPickResult ? parabolaPickResult->objectID : QUuid());
}
void ParabolaPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) {
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
if (parabolaPickResult) {
parabolaPickResult->type = type;
parabolaPickResult->objectID = id;
parabolaPickResult->intersection = intersection;
parabolaPickResult->distance = distance;
parabolaPickResult->surfaceNormal = surfaceNormal;
PickParabola parabola = PickParabola(parabolaPickResult->pickVariant);
parabolaPickResult->pickVariant["velocity"] = vec3toVariant((intersection - parabola.origin -
0.5f * parabola.acceleration * parabolaPickResult->parabolicDistance * parabolaPickResult->parabolicDistance) / parabolaPickResult->parabolicDistance);
}
}
ParabolaPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth,
bool isVisibleInSecondaryCamera, bool pathEnabled) :
StartEndRenderState(startID, endID)
{
render::Transaction transaction;
auto scene = qApp->getMain3DScene();
_pathID = scene->allocateID();
_pathWidth = pathWidth;
if (render::Item::isValidID(_pathID)) {
auto renderItem = std::make_shared<ParabolaRenderItem>(pathColor, pathAlpha, pathWidth, isVisibleInSecondaryCamera, pathEnabled);
// TODO: update bounds properly
renderItem->editBound().setBox(glm::vec3(-16000.0f), 32000.0f);
transaction.resetItem(_pathID, std::make_shared<ParabolaRenderItem::Payload>(renderItem));
scene->enqueueTransaction(transaction);
}
}
void ParabolaPointer::RenderState::cleanup() {
StartEndRenderState::cleanup();
if (render::Item::isValidID(_pathID)) {
render::Transaction transaction;
auto scene = qApp->getMain3DScene();
transaction.removeItem(_pathID);
scene->enqueueTransaction(transaction);
}
}
void ParabolaPointer::RenderState::disable() {
StartEndRenderState::disable();
if (render::Item::isValidID(_pathID)) {
render::Transaction transaction;
auto scene = qApp->getMain3DScene();
transaction.updateItem<ParabolaRenderItem>(_pathID, [](ParabolaRenderItem& item) {
item.setVisible(false);
});
scene->enqueueTransaction(transaction);
}
}
void ParabolaPointer::RenderState::editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled) {
if (render::Item::isValidID(_pathID)) {
render::Transaction transaction;
auto scene = qApp->getMain3DScene();
transaction.updateItem<ParabolaRenderItem>(_pathID, [color, alpha, width, isVisibleInSecondaryCamera, enabled](ParabolaRenderItem& item) {
item.setColor(color);
item.setAlpha(alpha);
item.setWidth(width);
item.setIsVisibleInSecondaryCamera(isVisibleInSecondaryCamera);
item.setEnabled(enabled);
item.updateKey();
});
scene->enqueueTransaction(transaction);
}
}
void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) {
StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult);
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
if (parabolaPickResult && render::Item::isValidID(_pathID)) {
render::Transaction transaction;
auto scene = qApp->getMain3DScene();
PickParabola parabola = PickParabola(parabolaPickResult->pickVariant);
glm::vec3 velocity = parabola.velocity;
glm::vec3 acceleration = parabola.acceleration;
float parabolicDistance = distance > 0.0f ? distance : parabolaPickResult->parabolicDistance;
float width = scaleWithAvatar ? getPathWidth() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale() : getPathWidth();
transaction.updateItem<ParabolaRenderItem>(_pathID, [origin, velocity, acceleration, parabolicDistance, width](ParabolaRenderItem& item) {
item.setVisible(true);
item.setOrigin(origin);
item.setVelocity(velocity);
item.setAcceleration(acceleration);
item.setParabolicDistance(parabolicDistance);
item.setWidth(width);
});
scene->enqueueTransaction(transaction);
}
}
std::shared_ptr<StartEndRenderState> ParabolaPointer::buildRenderState(const QVariantMap& propMap) {
QUuid startID;
if (propMap["start"].isValid()) {
QVariantMap startMap = propMap["start"].toMap();
if (startMap["type"].isValid()) {
startMap.remove("visible");
startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap);
}
}
glm::vec3 color = glm::vec3(RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR);
float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a;
float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH;
bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA;
bool enabled = false;
if (propMap["path"].isValid()) {
enabled = true;
QVariantMap pathMap = propMap["path"].toMap();
if (pathMap["color"].isValid()) {
bool valid;
color = toGlm(xColorFromVariant(pathMap["color"], valid));
}
if (pathMap["alpha"].isValid()) {
alpha = pathMap["alpha"].toFloat();
}
if (pathMap["width"].isValid()) {
width = pathMap["width"].toFloat();
}
if (pathMap["isVisibleInSecondaryCamera"].isValid()) {
isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool();
}
}
QUuid endID;
if (propMap["end"].isValid()) {
QVariantMap endMap = propMap["end"].toMap();
if (endMap["type"].isValid()) {
endMap.remove("visible");
endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap);
}
}
return std::make_shared<RenderState>(startID, endID, color, alpha, width, isVisibleInSecondaryCamera, enabled);
}
PointerEvent ParabolaPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) {
QUuid pickedID;
glm::vec3 intersection, surfaceNormal, origin, velocity, acceleration;
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
if (parabolaPickResult) {
intersection = parabolaPickResult->intersection;
surfaceNormal = parabolaPickResult->surfaceNormal;
const QVariantMap& parabola = parabolaPickResult->pickVariant;
origin = vec3FromVariant(parabola["origin"]);
velocity = vec3FromVariant(parabola["velocity"]);
acceleration = vec3FromVariant(parabola["acceleration"]);
pickedID = parabolaPickResult->objectID;
}
if (pickedID != target.objectID) {
intersection = findIntersection(target, origin, velocity, acceleration);
}
glm::vec2 pos2D = findPos2D(target, intersection);
// If we just started triggering and we haven't moved too much, don't update intersection and pos2D
TriggerState& state = hover ? _latestState : _states[button];
float sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale;
bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared;
if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) {
pos2D = state.triggerPos2D;
intersection = state.intersection;
surfaceNormal = state.surfaceNormal;
}
if (!withinDeadspot) {
state.deadspotExpired = true;
}
return PointerEvent(pos2D, intersection, surfaceNormal, velocity);
}
glm::vec3 ParabolaPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration) {
// TODO: implement
switch (pickedObject.type) {
case ENTITY:
//return ParabolaPick::intersectParabolaWithEntityXYPlane(pickedObject.objectID, origin, velocity, acceleration);
case OVERLAY:
//return ParabolaPick::intersectParabolaWithOverlayXYPlane(pickedObject.objectID, origin, velocity, acceleration);
default:
return glm::vec3(NAN);
}
}
ParabolaPointer::RenderState::ParabolaRenderItem::ParabolaRenderItem(const glm::vec3& color, float alpha, float width,
bool isVisibleInSecondaryCamera, bool enabled) :
_isVisibleInSecondaryCamera(isVisibleInSecondaryCamera), _enabled(enabled)
{
_uniformBuffer->resize(sizeof(ParabolaData));
setColor(color);
setAlpha(alpha);
setWidth(width);
updateKey();
}
void ParabolaPointer::RenderState::ParabolaRenderItem::setVisible(bool visible) {
if (visible && _enabled) {
_key = render::ItemKey::Builder(_key).withVisible();
} else {
_key = render::ItemKey::Builder(_key).withInvisible();
}
_visible = visible;
}
void ParabolaPointer::RenderState::ParabolaRenderItem::updateKey() {
// FIXME: There's no way to designate a render item as non-shadow-reciever, and since a parabola's bounding box covers the entire domain,
// it seems to block all shadows. I think this is a bug with shadows.
//auto builder = _parabolaData.color.a < 1.0f ? render::ItemKey::Builder::transparentShape() : render::ItemKey::Builder::opaqueShape();
auto builder = render::ItemKey::Builder::transparentShape();
if (_enabled && _visible) {
builder.withVisible();
} else {
builder.withInvisible();
}
if (_isVisibleInSecondaryCamera) {
builder.withTagBits(render::hifi::TAG_ALL_VIEWS);
} else {
builder.withTagBits(render::hifi::TAG_MAIN_VIEW);
}
_key = builder.build();
}
const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline() {
if (!_parabolaPipeline || !_transparentParabolaPipeline) {
auto vs = parabola_vert::getShader();
auto ps = parabola_frag::getShader();
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("parabolaData"), 0));
gpu::Shader::makeProgram(*program, slotBindings);
{
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setBlendFunction(false,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
PrepareStencil::testMaskDrawShape(*state);
state->setCullMode(gpu::State::CULL_NONE);
_parabolaPipeline = gpu::Pipeline::create(program, state);
}
{
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setBlendFunction(true,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
PrepareStencil::testMask(*state);
state->setCullMode(gpu::State::CULL_NONE);
_transparentParabolaPipeline = gpu::Pipeline::create(program, state);
}
}
return (_parabolaData.color.a < 1.0f ? _transparentParabolaPipeline : _parabolaPipeline);
}
void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args) {
if (!_visible) {
return;
}
gpu::Batch& batch = *(args->_batch);
Transform transform;
transform.setTranslation(_origin);
batch.setModelTransform(transform);
batch.setPipeline(getParabolaPipeline());
const int MAX_SECTIONS = 100;
if (glm::length2(_parabolaData.acceleration) < EPSILON) {
_parabolaData.numSections = 1;
} else {
_parabolaData.numSections = glm::clamp((int)(_parabolaData.parabolicDistance + 1) * 10, 1, MAX_SECTIONS);
}
updateUniformBuffer();
batch.setUniformBuffer(0, _uniformBuffer);
// We draw 2 * n + 2 vertices for a triangle strip
batch.draw(gpu::TRIANGLE_STRIP, 2 * _parabolaData.numSections + 2, 0);
}
namespace render {
template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) {
return payload->getKey();
}
template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) {
if (payload) {
return payload->getBound();
}
return Item::Bound();
}
template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args) {
if (payload) {
payload->render(args);
}
}
template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) {
return ShapeKey::Builder::ownPipeline();
}
}

View file

@ -0,0 +1,125 @@
//
// Created by Sam Gondelman 7/17/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
//
#ifndef hifi_ParabolaPointer_h
#define hifi_ParabolaPointer_h
#include "PathPointer.h"
class ParabolaPointer : public PathPointer {
using Parent = PathPointer;
public:
class RenderState : public StartEndRenderState {
public:
class ParabolaRenderItem {
public:
using Payload = render::Payload<ParabolaRenderItem>;
using Pointer = Payload::DataPointer;
ParabolaRenderItem(const glm::vec3& color, float alpha, float width,
bool isVisibleInSecondaryCamera, bool enabled);
~ParabolaRenderItem() {}
static gpu::PipelinePointer _parabolaPipeline;
static gpu::PipelinePointer _transparentParabolaPipeline;
const gpu::PipelinePointer getParabolaPipeline();
void render(RenderArgs* args);
render::Item::Bound& editBound() { return _bound; }
const render::Item::Bound& getBound() { return _bound; }
render::ItemKey getKey() const { return _key; }
void setVisible(bool visible);
void updateKey();
void updateUniformBuffer() { _uniformBuffer->setSubData(0, _parabolaData); }
void setColor(const glm::vec3& color) { _parabolaData.color = glm::vec4(color, _parabolaData.color.a); }
void setAlpha(const float& alpha) { _parabolaData.color.a = alpha; }
void setWidth(const float& width) { _parabolaData.width = width; }
void setParabolicDistance(const float& parabolicDistance) { _parabolaData.parabolicDistance = parabolicDistance; }
void setVelocity(const glm::vec3& velocity) { _parabolaData.velocity = velocity; }
void setAcceleration(const glm::vec3& acceleration) { _parabolaData.acceleration = acceleration; }
void setOrigin(const glm::vec3& origin) { _origin = origin; }
void setIsVisibleInSecondaryCamera(const bool& isVisibleInSecondaryCamera) { _isVisibleInSecondaryCamera = isVisibleInSecondaryCamera; }
void setEnabled(const bool& enabled) { _enabled = enabled; }
static const glm::vec4 DEFAULT_PARABOLA_COLOR;
static const float DEFAULT_PARABOLA_WIDTH;
static const bool DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA;
private:
render::Item::Bound _bound;
render::ItemKey _key;
glm::vec3 _origin { 0.0f };
bool _isVisibleInSecondaryCamera { DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA };
bool _visible { false };
bool _enabled { false };
struct ParabolaData {
glm::vec3 velocity { 0.0f };
float parabolicDistance { 0.0f };
vec3 acceleration { 0.0f };
float width { DEFAULT_PARABOLA_WIDTH };
vec4 color { vec4(DEFAULT_PARABOLA_COLOR)};
int numSections { 0 };
ivec3 spare;
};
ParabolaData _parabolaData;
gpu::BufferPointer _uniformBuffer { std::make_shared<gpu::Buffer>() };
};
RenderState() {}
RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth,
bool isVisibleInSecondaryCamera, bool pathEnabled);
void setPathWidth(float width) { _pathWidth = width; }
float getPathWidth() const { return _pathWidth; }
void cleanup() override;
void disable() override;
void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override;
void editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled);
private:
int _pathID;
float _pathWidth;
};
ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers,
bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
static std::shared_ptr<StartEndRenderState> buildRenderState(const QVariantMap& propMap);
protected:
void editRenderStatePath(const std::string& state, const QVariant& pathProps) override;
glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override;
glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) const override;
glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const override;
IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const override;
QUuid getPickedObjectID(const PickResultPointer& pickResult) const override;
void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) override;
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
private:
static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration);
};
namespace render {
template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload);
template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload);
template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args);
template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload);
}
#endif // hifi_ParabolaPointer_h

View file

@ -0,0 +1,353 @@
//
// Created by Sam Gondelman 7/17/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
//
#include "PathPointer.h"
#include "Application.h"
#include "avatar/AvatarManager.h"
#include <DependencyManager.h>
#include <PickManager.h>
#include "PickScriptingInterface.h"
#include "RayPick.h"
PathPointer::PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd,
bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) :
Pointer(DependencyManager::get<PickScriptingInterface>()->createPick(type, rayProps), enabled, hover),
_renderStates(renderStates),
_defaultRenderStates(defaultRenderStates),
_triggers(triggers),
_faceAvatar(faceAvatar),
_followNormal(followNormal),
_followNormalStrength(followNormalStrength),
_centerEndY(centerEndY),
_lockEnd(lockEnd),
_distanceScaleEnd(distanceScaleEnd),
_scaleWithAvatar(scaleWithAvatar)
{
for (auto& state : _renderStates) {
if (!enabled || state.first != _currentRenderState) {
state.second->disable();
}
}
for (auto& state : _defaultRenderStates) {
if (!enabled || state.first != _currentRenderState) {
state.second.second->disable();
}
}
}
PathPointer::~PathPointer() {
for (auto& renderState : _renderStates) {
renderState.second->cleanup();
}
for (auto& renderState : _defaultRenderStates) {
renderState.second.second->cleanup();
}
}
void PathPointer::setRenderState(const std::string& state) {
withWriteLock([&] {
if (!_currentRenderState.empty() && state != _currentRenderState) {
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
_renderStates[_currentRenderState]->disable();
}
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
_defaultRenderStates[_currentRenderState].second->disable();
}
}
_currentRenderState = state;
});
}
void PathPointer::setLength(float length) {
withWriteLock([&] {
_pathLength = length;
});
}
void PathPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) {
withWriteLock([&] {
_lockEndObject.id = objectID;
_lockEndObject.isOverlay = isOverlay;
_lockEndObject.offsetMat = offsetMat;
});
}
PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pickResult) {
PickResultPointer visualPickResult = pickResult;
glm::vec3 origin = getPickOrigin(pickResult);
IntersectionType type = getPickedObjectType(pickResult);
QUuid id;
glm::vec3 intersection;
float distance;
glm::vec3 surfaceNormal;
if (type != IntersectionType::HUD) {
glm::vec3 endVec;
if (!_lockEndObject.id.isNull()) {
glm::vec3 pos;
glm::quat rot;
glm::vec3 dim;
glm::vec3 registrationPoint;
if (_lockEndObject.isOverlay) {
pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value);
rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value);
dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value);
registrationPoint = glm::vec3(0.5f);
} else {
EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_lockEndObject.id);
glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition());
glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat;
pos = extractTranslation(finalPosAndRotMat);
rot = glmExtractRotation(finalPosAndRotMat);
dim = props.getDimensions();
registrationPoint = props.getRegistrationPoint();
}
const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);
endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint));
glm::vec3 direction = endVec - origin;
distance = glm::distance(origin, endVec);
glm::vec3 normalizedDirection = glm::normalize(direction);
type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY;
id = _lockEndObject.id;
intersection = endVec;
surfaceNormal = -normalizedDirection;
setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal);
} else if (type != IntersectionType::NONE && _lockEnd) {
id = getPickedObjectID(pickResult);
if (type == IntersectionType::ENTITY) {
endVec = DependencyManager::get<EntityScriptingInterface>()->getEntityTransform(id)[3];
} else if (type == IntersectionType::OVERLAY) {
endVec = vec3FromVariant(qApp->getOverlays().getProperty(id, "position").value);
} else if (type == IntersectionType::AVATAR) {
endVec = DependencyManager::get<AvatarHashMap>()->getAvatar(id)->getPosition();
}
glm::vec3 direction = endVec - origin;
distance = glm::distance(origin, endVec);
glm::vec3 normalizedDirection = glm::normalize(direction);
intersection = endVec;
surfaceNormal = -normalizedDirection;
setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal);
}
}
return visualPickResult;
}
void PathPointer::updateVisuals(const PickResultPointer& pickResult) {
IntersectionType type = getPickedObjectType(pickResult);
if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
(type != IntersectionType::NONE || _pathLength > 0.0f)) {
glm::vec3 origin = getPickOrigin(pickResult);
glm::vec3 end = getPickEnd(pickResult, _pathLength);
glm::vec3 surfaceNormal = getPickedObjectNormal(pickResult);
_renderStates[_currentRenderState]->update(origin, end, surfaceNormal, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar,
_followNormal, _followNormalStrength, _pathLength, pickResult);
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
_defaultRenderStates[_currentRenderState].second->disable();
}
} else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
_renderStates[_currentRenderState]->disable();
}
glm::vec3 origin = getPickOrigin(pickResult);
glm::vec3 end = getPickEnd(pickResult, _defaultRenderStates[_currentRenderState].first);
_defaultRenderStates[_currentRenderState].second->update(origin, end, Vectors::UP, _scaleWithAvatar, _distanceScaleEnd, _centerEndY,
_faceAvatar, _followNormal, _followNormalStrength, _defaultRenderStates[_currentRenderState].first, pickResult);
} else if (!_currentRenderState.empty()) {
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
_renderStates[_currentRenderState]->disable();
}
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
_defaultRenderStates[_currentRenderState].second->disable();
}
}
}
void PathPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) {
withWriteLock([&] {
updateRenderStateOverlay(_renderStates[state]->getStartID(), startProps);
updateRenderStateOverlay(_renderStates[state]->getEndID(), endProps);
QVariant startDim = startProps.toMap()["dimensions"];
if (startDim.isValid()) {
_renderStates[state]->setStartDim(vec3FromVariant(startDim));
}
QVariant endDim = endProps.toMap()["dimensions"];
if (endDim.isValid()) {
_renderStates[state]->setEndDim(vec3FromVariant(endDim));
}
QVariant rotation = endProps.toMap()["rotation"];
if (rotation.isValid()) {
_renderStates[state]->setEndRot(quatFromVariant(rotation));
}
editRenderStatePath(state, pathProps);
});
}
void PathPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
if (!id.isNull() && props.isValid()) {
QVariantMap propMap = props.toMap();
propMap.remove("visible");
qApp->getOverlays().editOverlay(id, propMap);
}
}
Pointer::PickedObject PathPointer::getHoveredObject(const PickResultPointer& pickResult) {
return PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult));
}
Pointer::Buttons PathPointer::getPressedButtons(const PickResultPointer& pickResult) {
std::unordered_set<std::string> toReturn;
for (const PointerTrigger& trigger : _triggers) {
std::string button = trigger.getButton();
TriggerState& state = _states[button];
// TODO: right now, LaserPointers don't support axes, only on/off buttons
if (trigger.getEndpoint()->peek() >= 1.0f) {
toReturn.insert(button);
if (_previousButtons.find(button) == _previousButtons.end()) {
// start triggering for buttons that were just pressed
state.triggeredObject = PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult));
state.intersection = getPickEnd(pickResult);
state.triggerPos2D = findPos2D(state.triggeredObject, state.intersection);
state.triggerStartTime = usecTimestampNow();
state.surfaceNormal = getPickedObjectNormal(pickResult);
state.deadspotExpired = false;
state.wasTriggering = true;
state.triggering = true;
_latestState = state;
}
} else {
// stop triggering for buttons that aren't pressed
state.wasTriggering = state.triggering;
state.triggering = false;
_latestState = state;
}
}
_previousButtons = toReturn;
return toReturn;
}
StartEndRenderState::StartEndRenderState(const OverlayID& startID, const OverlayID& endID) :
_startID(startID), _endID(endID) {
if (!_startID.isNull()) {
_startDim = vec3FromVariant(qApp->getOverlays().getProperty(_startID, "dimensions").value);
_startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool();
}
if (!_endID.isNull()) {
_endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value);
_endRot = quatFromVariant(qApp->getOverlays().getProperty(_endID, "rotation").value);
_endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool();
}
}
void StartEndRenderState::cleanup() {
if (!_startID.isNull()) {
qApp->getOverlays().deleteOverlay(_startID);
}
if (!_endID.isNull()) {
qApp->getOverlays().deleteOverlay(_endID);
}
}
void StartEndRenderState::disable() {
if (!getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("visible", false);
startProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(getStartID(), startProps);
}
if (!getEndID().isNull()) {
QVariantMap endProps;
endProps.insert("visible", false);
endProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(getEndID(), endProps);
}
}
void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) {
if (!getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("position", vec3toVariant(origin));
startProps.insert("visible", true);
if (scaleWithAvatar) {
startProps.insert("dimensions", vec3toVariant(getStartDim() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale()));
}
startProps.insert("ignoreRayIntersection", doesStartIgnoreRays());
qApp->getOverlays().editOverlay(getStartID(), startProps);
}
if (!getEndID().isNull()) {
QVariantMap endProps;
glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(getEndID(), "dimensions").value);
if (distanceScaleEnd) {
dim = getEndDim() * glm::distance(origin, end);
endProps.insert("dimensions", vec3toVariant(dim));
} else if (scaleWithAvatar) {
dim = getEndDim() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
endProps.insert("dimensions", vec3toVariant(dim));
}
glm::quat normalQuat = Quat().lookAtSimple(Vectors::ZERO, surfaceNormal);
normalQuat = normalQuat * glm::quat(glm::vec3(-M_PI_2, 0, 0));
glm::vec3 avatarUp = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * Vectors::UP;
glm::quat rotation = glm::rotation(Vectors::UP, avatarUp);
glm::vec3 position = end;
if (!centerEndY) {
if (followNormal) {
position = end + 0.5f * dim.y * surfaceNormal;
} else {
position = end + 0.5f * dim.y * avatarUp;
}
}
if (faceAvatar) {
glm::quat orientation = followNormal ? normalQuat : DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation();
glm::quat lookAtWorld = Quat().lookAt(position, DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldPosition(), surfaceNormal);
glm::quat lookAtModel = glm::inverse(orientation) * lookAtWorld;
glm::quat lookAtFlatModel = Quat().cancelOutRollAndPitch(lookAtModel);
glm::quat lookAtFlatWorld = orientation * lookAtFlatModel;
rotation = lookAtFlatWorld;
} else if (followNormal) {
rotation = normalQuat;
}
if (followNormal && followNormalStrength > 0.0f && followNormalStrength < 1.0f) {
if (!_avgEndRotInitialized) {
_avgEndRot = rotation;
_avgEndRotInitialized = true;
} else {
rotation = glm::slerp(_avgEndRot, rotation, followNormalStrength);
if (!centerEndY) {
position = end + 0.5f * dim.y * (rotation * Vectors::UP);
}
_avgEndRot = rotation;
}
}
endProps.insert("position", vec3toVariant(position));
endProps.insert("rotation", quatToVariant(rotation));
endProps.insert("visible", true);
endProps.insert("ignoreRayIntersection", doesEndIgnoreRays());
qApp->getOverlays().editOverlay(getEndID(), endProps);
}
}
glm::vec2 PathPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) {
switch (pickedObject.type) {
case ENTITY:
return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin);
case OVERLAY:
return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin);
case HUD:
return DependencyManager::get<PickManager>()->calculatePos2DFromHUD(origin);
default:
return glm::vec2(NAN);
}
}

View file

@ -0,0 +1,135 @@
//
// Created by Sam Gondelman 7/17/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
//
#ifndef hifi_PathPointer_h
#define hifi_PathPointer_h
#include <QVariant>
#include <QString>
#include <glm/glm.hpp>
#include "ui/overlays/Overlay.h"
#include <Pointer.h>
#include <Pick.h>
struct LockEndObject {
QUuid id { QUuid() };
bool isOverlay { false };
glm::mat4 offsetMat { glm::mat4() };
};
class StartEndRenderState {
public:
StartEndRenderState() {}
StartEndRenderState(const OverlayID& startID, const OverlayID& endID);
const OverlayID& getStartID() const { return _startID; }
const OverlayID& getEndID() const { return _endID; }
const bool& doesStartIgnoreRays() const { return _startIgnoreRays; }
const bool& doesEndIgnoreRays() const { return _endIgnoreRays; }
void setStartDim(const glm::vec3& startDim) { _startDim = startDim; }
const glm::vec3& getStartDim() const { return _startDim; }
void setEndDim(const glm::vec3& endDim) { _endDim = endDim; }
const glm::vec3& getEndDim() const { return _endDim; }
void setEndRot(const glm::quat& endRot) { _endRot = endRot; }
const glm::quat& getEndRot() const { return _endRot; }
virtual void cleanup();
virtual void disable();
virtual void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult);
protected:
OverlayID _startID;
OverlayID _endID;
bool _startIgnoreRays;
bool _endIgnoreRays;
glm::vec3 _startDim;
glm::vec3 _endDim;
glm::quat _endRot;
glm::quat _avgEndRot;
bool _avgEndRotInitialized { false };
};
typedef std::unordered_map<std::string, std::shared_ptr<StartEndRenderState>> RenderStateMap;
typedef std::unordered_map<std::string, std::pair<float, std::shared_ptr<StartEndRenderState>>> DefaultRenderStateMap;
class PathPointer : public Pointer {
using Parent = Pointer;
public:
PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd,
bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
virtual ~PathPointer();
void setRenderState(const std::string& state) override;
// You cannot use editRenderState to change the type of any part of the pointer. You can only edit the properties of the existing overlays.
void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override;
void setLength(float length) override;
void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override;
void updateVisuals(const PickResultPointer& prevRayPickResult) override;
protected:
RenderStateMap _renderStates;
DefaultRenderStateMap _defaultRenderStates;
std::string _currentRenderState { "" };
PointerTriggers _triggers;
float _pathLength { 0.0f };
bool _faceAvatar;
bool _followNormal;
float _followNormalStrength;
bool _centerEndY;
bool _lockEnd;
bool _distanceScaleEnd;
bool _scaleWithAvatar;
LockEndObject _lockEndObject;
struct TriggerState {
PickedObject triggeredObject;
glm::vec3 intersection { NAN };
glm::vec3 surfaceNormal { NAN };
glm::vec2 triggerPos2D { NAN };
quint64 triggerStartTime { 0 };
bool deadspotExpired { true };
bool triggering { false };
bool wasTriggering { false };
};
Pointer::Buttons _previousButtons;
std::unordered_map<std::string, TriggerState> _states;
TriggerState _latestState;
bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
void updateRenderStateOverlay(const OverlayID& id, const QVariant& props);
virtual void editRenderStatePath(const std::string& state, const QVariant& pathProps) = 0;
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override;
PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override;
virtual glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const = 0;
virtual glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance = 0.0f) const = 0;
virtual glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const = 0;
virtual IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const = 0;
virtual QUuid getPickedObjectID(const PickResultPointer& pickResult) const = 0;
virtual void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) = 0;
static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin);
};
#endif // hifi_PathPointer_h

View file

@ -17,6 +17,9 @@
#include "JointRayPick.h"
#include "MouseRayPick.h"
#include "StylusPick.h"
#include "StaticParabolaPick.h"
#include "JointParabolaPick.h"
#include "MouseParabolaPick.h"
#include <ScriptEngine.h>
@ -26,6 +29,8 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type,
return createRayPick(properties);
case PickQuery::PickType::Stylus:
return createStylusPick(properties);
case PickQuery::PickType::Parabola:
return createParabolaPick(properties);
default:
return PickManager::INVALID_PICK_ID;
}
@ -134,6 +139,101 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties
return DependencyManager::get<PickManager>()->addPick(PickQuery::Stylus, std::make_shared<StylusPick>(side, filter, maxDistance, enabled));
}
/**jsdoc
* A set of properties that can be passed to {@link Picks.createPick} to create a new Parabola Pick.
* @typedef {object} Picks.ParabolaPickProperties
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
* @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
* @property {string} [joint] Only for Joint or Mouse Parabola Picks. If "Mouse", it will create a Parabola Pick that follows the system mouse, in desktop or HMD.
* If "Avatar", it will create a Joint Parabola Pick that follows your avatar's head. Otherwise, it will create a Joint Parabola Pick that follows the given joint, if it
* exists on your current avatar.
* @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Parabola Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral
* @property {Vec3} [dirOffset=Vec3.UP] Only for Joint Parabola Picks. A local joint direction offset. x = upward, y = forward, z = lateral
* @property {Vec3} [position] Only for Static Parabola Picks. The world-space origin of the parabola segment.
* @property {Vec3} [direction=-Vec3.FRONT] Only for Static Parabola Picks. The world-space direction of the parabola segment.
* @property {number} [speed=1] The initial speed of the parabola, i.e. the initial speed of the projectile whose trajectory defines the parabola.
* @property {Vec3} [accelerationAxis=-Vec3.UP] The acceleration of the parabola, i.e. the acceleration of the projectile whose trajectory defines the parabola, both magnitude and direction.
* @property {boolean} [rotateAccelerationWithAvatar=true] Whether or not the acceleration axis should rotate with your avatar's local Y axis.
* @property {boolean} [scaleWithAvatar=false] If true, the velocity and acceleration of the Pick will scale linearly with your avatar.
*/
unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properties) {
QVariantMap propMap = properties.toMap();
bool enabled = false;
if (propMap["enabled"].isValid()) {
enabled = propMap["enabled"].toBool();
}
PickFilter filter = PickFilter();
if (propMap["filter"].isValid()) {
filter = PickFilter(propMap["filter"].toUInt());
}
float maxDistance = 0.0f;
if (propMap["maxDistance"].isValid()) {
maxDistance = propMap["maxDistance"].toFloat();
}
float speed = 1.0f;
if (propMap["speed"].isValid()) {
speed = propMap["speed"].toFloat();
}
glm::vec3 accelerationAxis = -Vectors::UP;
if (propMap["accelerationAxis"].isValid()) {
accelerationAxis = vec3FromVariant(propMap["accelerationAxis"]);
}
bool rotateAccelerationWithAvatar = true;
if (propMap["rotateAccelerationWithAvatar"].isValid()) {
rotateAccelerationWithAvatar = propMap["rotateAccelerationWithAvatar"].toBool();
}
bool scaleWithAvatar = false;
if (propMap["scaleWithAvatar"].isValid()) {
scaleWithAvatar = propMap["scaleWithAvatar"].toBool();
}
if (propMap["joint"].isValid()) {
std::string jointName = propMap["joint"].toString().toStdString();
if (jointName != "Mouse") {
// x = upward, y = forward, z = lateral
glm::vec3 posOffset = Vectors::ZERO;
if (propMap["posOffset"].isValid()) {
posOffset = vec3FromVariant(propMap["posOffset"]);
}
glm::vec3 dirOffset = Vectors::UP;
if (propMap["dirOffset"].isValid()) {
dirOffset = vec3FromVariant(propMap["dirOffset"]);
}
return DependencyManager::get<PickManager>()->addPick(PickQuery::Parabola, std::make_shared<JointParabolaPick>(jointName, posOffset, dirOffset,
speed, accelerationAxis, rotateAccelerationWithAvatar,
scaleWithAvatar, filter, maxDistance, enabled));
} else {
return DependencyManager::get<PickManager>()->addPick(PickQuery::Parabola, std::make_shared<MouseParabolaPick>(speed, accelerationAxis, rotateAccelerationWithAvatar,
scaleWithAvatar, filter, maxDistance, enabled));
}
} else if (propMap["position"].isValid()) {
glm::vec3 position = vec3FromVariant(propMap["position"]);
glm::vec3 direction = -Vectors::FRONT;
if (propMap["direction"].isValid()) {
direction = vec3FromVariant(propMap["direction"]);
}
return DependencyManager::get<PickManager>()->addPick(PickQuery::Parabola, std::make_shared<StaticParabolaPick>(position, direction, speed, accelerationAxis,
rotateAccelerationWithAvatar, scaleWithAvatar,
filter, maxDistance, enabled));
}
return PickManager::INVALID_PICK_ID;
}
void PickScriptingInterface::enablePick(unsigned int uid) {
DependencyManager::get<PickManager>()->enablePick(uid);
}

View file

@ -62,6 +62,7 @@ class PickScriptingInterface : public QObject, public Dependency {
public:
unsigned int createRayPick(const QVariant& properties);
unsigned int createStylusPick(const QVariant& properties);
unsigned int createParabolaPick(const QVariant& properties);
void registerMetaTypes(QScriptEngine* engine);
@ -71,7 +72,7 @@ public:
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick.
* @function Picks.createPick
* @param {PickType} type A PickType that specifies the method of picking to use
* @param {Picks.RayPickProperties|Picks.StylusPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
* @param {Picks.RayPickProperties|Picks.StylusPickProperties|Picks.ParabolaPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
* @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid.
*/
Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties);
@ -125,6 +126,21 @@ public:
* @property {StylusTip} stylusTip The StylusTip that was used. Valid even if there was no intersection.
*/
/**jsdoc
* An intersection result for a Parabola Pick.
*
* @typedef {object} ParabolaPickResult
* @property {number} type The intersection type.
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
* @property {number} distance The distance to the intersection point from the origin of the parabola, not along the parabola.
* @property {number} parabolicDistance The distance to the intersection point from the origin of the parabola, along the parabola.
* @property {Vec3} intersection The intersection point in world-space.
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
* @property {StylusTip} parabola The PickParabola that was used. Valid even if there was no intersection.
*/
/**jsdoc
* Get the most recent pick result from this Pick. This will be updated as long as the Pick is enabled.
* @function Picks.getPrevPickResult
@ -162,7 +178,7 @@ public:
* Check if a Pick is associated with the left hand.
* @function Picks.isLeftHand
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0.
* @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0.
*/
Q_INVOKABLE bool isLeftHand(unsigned int uid);
@ -170,7 +186,7 @@ public:
* Check if a Pick is associated with the right hand.
* @function Picks.isRightHand
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1.
* @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1.
*/
Q_INVOKABLE bool isRightHand(unsigned int uid);
@ -178,7 +194,7 @@ public:
* Check if a Pick is associated with the system mouse.
* @function Picks.isMouse
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @returns {boolean} True if the Pick is a Mouse Ray Pick, false otherwise.
* @returns {boolean} True if the Pick is a Mouse Ray or Parabola Pick, false otherwise.
*/
Q_INVOKABLE bool isMouse(unsigned int uid);

View file

@ -15,6 +15,7 @@
#include "Application.h"
#include "LaserPointer.h"
#include "StylusPointer.h"
#include "ParabolaPointer.h"
void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const {
DependencyManager::get<PointerManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
@ -37,6 +38,8 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType&
return createLaserPointer(properties);
case PickQuery::PickType::Stylus:
return createStylus(properties);
case PickQuery::PickType::Parabola:
return createParabolaPointer(properties);
default:
return PointerEvent::INVALID_POINTER_ID;
}
@ -84,27 +87,22 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties)
* @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field).
* An overlay to represent the end of the Ray Pointer, if desired.
*/
/**jsdoc
* A trigger mechanism for Ray Pointers.
*
* @typedef {object} Pointers.Trigger
* @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger <code>button</code>.
* @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
* it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".
*/
/**jsdoc
* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
* @typedef {object} Pointers.LaserPointerProperties
* @property {boolean} [faceAvatar=false] Ray Pointers only. If true, the end of the Pointer will always rotate to face the avatar.
* @property {boolean} [centerEndY=true] Ray Pointers only. If false, the end of the Pointer will be moved up by half of its height.
* @property {boolean} [lockEnd=false] Ray Pointers only. If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
* @property {boolean} [distanceScaleEnd=false] Ray Pointers only. If true, the dimensions of the end of the Pointer will scale linearly with distance.
* @property {boolean} [scaleWithAvatar=false] Ray Pointers only. If true, the width of the Pointer's path will scale linearly with your avatar's scale.
* @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar.
* @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height.
* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
* @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance.
* @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale.
* @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface.
* @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. <code>0-1</code>. If 0 or 1,
* the normal will follow exactly.
* @property {boolean} [enabled=false]
* @property {Pointers.RayPointerRenderState[]} [renderStates] Ray Pointers only. A list of different visual states to switch between.
* @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] Ray Pointers only. A list of different visual states to use if there is no intersection.
* @property {Pointers.RayPointerRenderState[]} [renderStates] A list of different visual states to switch between.
* @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection.
* @property {boolean} [hover=false] If this Pointer should generate hover events.
* @property {Pointers.Trigger[]} [triggers] Ray Pointers only. A list of different triggers mechanisms that control this Pointer's click event generation.
* @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation.
*/
unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const {
QVariantMap propertyMap = properties.toMap();
@ -134,12 +132,21 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool();
}
bool followNormal = false;
if (propertyMap["followNormal"].isValid()) {
followNormal = propertyMap["followNormal"].toBool();
}
float followNormalStrength = 0.0f;
if (propertyMap["followNormalStrength"].isValid()) {
followNormalStrength = propertyMap["followNormalStrength"].toFloat();
}
bool enabled = false;
if (propertyMap["enabled"].isValid()) {
enabled = propertyMap["enabled"].toBool();
}
LaserPointer::RenderStateMap renderStates;
RenderStateMap renderStates;
if (propertyMap["renderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["renderStates"].toList();
for (const QVariant& renderStateVariant : renderStateVariants) {
@ -153,7 +160,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
}
}
LaserPointer::DefaultRenderStateMap defaultRenderStates;
DefaultRenderStateMap defaultRenderStates;
if (propertyMap["defaultRenderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["defaultRenderStates"].toList();
for (const QVariant& renderStateVariant : renderStateVariants) {
@ -162,7 +169,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) {
std::string name = renderStateMap["name"].toString().toStdString();
float distance = renderStateMap["distance"].toFloat();
defaultRenderStates[name] = std::pair<float, RenderState>(distance, LaserPointer::buildRenderState(renderStateMap));
defaultRenderStates[name] = std::pair<float, std::shared_ptr<StartEndRenderState>>(distance, LaserPointer::buildRenderState(renderStateMap));
}
}
}
@ -192,7 +199,151 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
}
return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<LaserPointer>(properties, renderStates, defaultRenderStates, hover, triggers,
faceAvatar, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled));
faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd,
distanceScaleEnd, scaleWithAvatar, enabled));
}
/**jsdoc
* The rendering properties of the parabolic path
*
* @typedef {object} Pointers.ParabolaProperties
* @property {Color} color The color of the parabola.
* @property {number} alpha The alpha of the parabola.
* @property {number} width The width of the parabola, in meters.
*/
/**jsdoc
* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.ParabolaPointerRenderState},
* but with an additional distance field.
*
* @typedef {object} Pointers.DefaultParabolaPointerRenderState
* @augments Pointers.ParabolaPointerRenderState
* @property {number} distance The distance along the parabola at which to render the end of this Parabola Pointer, if one is defined.
*/
/**jsdoc
* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is intersecting something.
*
* @typedef {object} Pointers.ParabolaPointerRenderState
* @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState}
* @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field).
* An overlay to represent the beginning of the Parabola Pointer, if desired.
* @property {Pointers.ParabolaProperties} [path] The rendering properties of the parabolic path defined by the Parabola Pointer.
* @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field).
* An overlay to represent the end of the Parabola Pointer, if desired.
*/
/**jsdoc
* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
* @typedef {object} Pointers.LaserPointerProperties
* @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar.
* @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height.
* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
* @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance.
* @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale.
* @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface.
* @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. <code>0-1</code>. If 0 or 1,
* the normal will follow exactly.
* @property {boolean} [enabled=false]
* @property {Pointers.ParabolaPointerRenderState[]} [renderStates] A list of different visual states to switch between.
* @property {Pointers.DefaultParabolaPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection.
* @property {boolean} [hover=false] If this Pointer should generate hover events.
* @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation.
*/
unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& properties) const {
QVariantMap propertyMap = properties.toMap();
bool faceAvatar = false;
if (propertyMap["faceAvatar"].isValid()) {
faceAvatar = propertyMap["faceAvatar"].toBool();
}
bool centerEndY = true;
if (propertyMap["centerEndY"].isValid()) {
centerEndY = propertyMap["centerEndY"].toBool();
}
bool lockEnd = false;
if (propertyMap["lockEnd"].isValid()) {
lockEnd = propertyMap["lockEnd"].toBool();
}
bool distanceScaleEnd = false;
if (propertyMap["distanceScaleEnd"].isValid()) {
distanceScaleEnd = propertyMap["distanceScaleEnd"].toBool();
}
bool scaleWithAvatar = false;
if (propertyMap["scaleWithAvatar"].isValid()) {
scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool();
}
bool followNormal = false;
if (propertyMap["followNormal"].isValid()) {
followNormal = propertyMap["followNormal"].toBool();
}
float followNormalStrength = 0.0f;
if (propertyMap["followNormalStrength"].isValid()) {
followNormalStrength = propertyMap["followNormalStrength"].toFloat();
}
bool enabled = false;
if (propertyMap["enabled"].isValid()) {
enabled = propertyMap["enabled"].toBool();
}
RenderStateMap renderStates;
if (propertyMap["renderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["renderStates"].toList();
for (const QVariant& renderStateVariant : renderStateVariants) {
if (renderStateVariant.isValid()) {
QVariantMap renderStateMap = renderStateVariant.toMap();
if (renderStateMap["name"].isValid()) {
std::string name = renderStateMap["name"].toString().toStdString();
renderStates[name] = ParabolaPointer::buildRenderState(renderStateMap);
}
}
}
}
DefaultRenderStateMap defaultRenderStates;
if (propertyMap["defaultRenderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["defaultRenderStates"].toList();
for (const QVariant& renderStateVariant : renderStateVariants) {
if (renderStateVariant.isValid()) {
QVariantMap renderStateMap = renderStateVariant.toMap();
if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) {
std::string name = renderStateMap["name"].toString().toStdString();
float distance = renderStateMap["distance"].toFloat();
defaultRenderStates[name] = std::pair<float, std::shared_ptr<StartEndRenderState>>(distance, ParabolaPointer::buildRenderState(renderStateMap));
}
}
}
}
bool hover = false;
if (propertyMap["hover"].isValid()) {
hover = propertyMap["hover"].toBool();
}
PointerTriggers triggers;
auto userInputMapper = DependencyManager::get<UserInputMapper>();
if (propertyMap["triggers"].isValid()) {
QList<QVariant> triggerVariants = propertyMap["triggers"].toList();
for (const QVariant& triggerVariant : triggerVariants) {
if (triggerVariant.isValid()) {
QVariantMap triggerMap = triggerVariant.toMap();
if (triggerMap["action"].isValid() && triggerMap["button"].isValid()) {
controller::Endpoint::Pointer endpoint = userInputMapper->endpointFor(controller::Input(triggerMap["action"].toUInt()));
if (endpoint) {
std::string button = triggerMap["button"].toString().toStdString();
triggers.emplace_back(endpoint, button);
}
}
}
}
}
return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<ParabolaPointer>(properties, renderStates, defaultRenderStates, hover, triggers,
faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd, distanceScaleEnd,
scaleWithAvatar, enabled));
}
void PointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const {

View file

@ -31,14 +31,23 @@ class PointerScriptingInterface : public QObject, public Dependency {
public:
unsigned int createLaserPointer(const QVariant& properties) const;
unsigned int createStylus(const QVariant& properties) const;
unsigned int createParabolaPointer(const QVariant& properties) const;
/**jsdoc
* A trigger mechanism for Ray and Parabola Pointers.
*
* @typedef {object} Pointers.Trigger
* @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger <code>button</code>.
* @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
* it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".
*/
/**jsdoc
* Adds a new Pointer
* Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pointer, a Mouse Ray Pointer, or a Joint Ray Pointer.
* @function Pointers.createPointer
* @param {PickType} type A PickType that specifies the method of picking to use
* @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer <b>and</b> the {@link Picks.PickProperties} for the Pick that
* @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer <b>and</b> the {@link Picks.PickProperties} for the Pick that
* this Pointer will use to do its picking.
* @returns {number} The ID of the created Pointer. Used for managing the Pointer. 0 if invalid.
*

View file

@ -39,7 +39,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) {
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
if (avatarRes.intersects) {
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, glm::vec3(NAN), avatarRes.extraInfo);
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo);
} else {
return std::make_shared<RayPickResult>(pick.toVariantMap());
}

View file

@ -19,7 +19,7 @@ public:
RayPickResult() {}
RayPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) :
PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal), extraInfo(extraInfo) {
PickResult(searchRay.toVariantMap()), extraInfo(extraInfo), objectID(objectID), intersection(intersection), surfaceNormal(surfaceNormal), type(type), distance(distance), intersects(type != NONE) {
}
RayPickResult(const RayPickResult& rayPickResult) : PickResult(rayPickResult.pickVariant) {
@ -32,13 +32,13 @@ public:
extraInfo = rayPickResult.extraInfo;
}
IntersectionType type { NONE };
bool intersects { false };
QVariantMap extraInfo;
QUuid objectID;
float distance { FLT_MAX };
glm::vec3 intersection { NAN };
glm::vec3 surfaceNormal { NAN };
QVariantMap extraInfo;
IntersectionType type { NONE };
float distance { FLT_MAX };
bool intersects { false };
virtual QVariantMap toVariantMap() const override {
QVariantMap toReturn;

View file

@ -0,0 +1,19 @@
//
// Created by Sam Gondelman 7/2/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
//
#include "StaticParabolaPick.h"
StaticParabolaPick::StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis,
bool scaleWithAvatar, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) :
ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled),
_position(position), _velocity(direction)
{
}
PickParabola StaticParabolaPick::getMathematicalPick() const {
return PickParabola(_position, getSpeed() * _velocity, getAcceleration());
}

View file

@ -0,0 +1,26 @@
//
// Created by Sam Gondelman 7/2/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
//
#ifndef hifi_StaticParabolaPick_h
#define hifi_StaticParabolaPick_h
#include "ParabolaPick.h"
class StaticParabolaPick : public ParabolaPick {
public:
StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar,
bool scaleWithAvatar, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
PickParabola getMathematicalPick() const override;
private:
glm::vec3 _position;
glm::vec3 _velocity;
};
#endif // hifi_StaticParabolaPick_h

View file

@ -56,6 +56,7 @@ Audio::Audio() : _devices(_contextIsHMD) {
connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume);
connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
enableNoiseReduction(enableNoiseReductionSetting.get());
onContextChanged();
}
bool Audio::startRecording(const QString& filepath) {

View file

@ -35,6 +35,12 @@ glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& p
return result;
}
glm::vec3 HMDScriptingInterface::calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const {
glm::vec3 result;
qApp->getApplicationCompositor().calculateParabolaUICollisionPoint(position, velocity, acceleration, result, parabolicDistance);
return result;
}
glm::vec2 HMDScriptingInterface::overlayFromWorldPoint(const glm::vec3& position) const {
return qApp->getApplicationCompositor().overlayFromSphereSurface(position);
}

View file

@ -100,6 +100,8 @@ public:
*/
Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const;
glm::vec3 calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const;
/**jsdoc
* Get the 2D HUD overlay coordinates of a 3D point on the HUD overlay.
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.

View file

@ -86,10 +86,6 @@ void WindowScriptingInterface::raise() {
});
}
void WindowScriptingInterface::raiseMainWindow() {
raise();
}
/// Display an alert box
/// \param const QString& message message to display
/// \return QScriptValue::UndefinedValue

View file

@ -80,13 +80,6 @@ public slots:
*/
void raise();
/**jsdoc
* Raise the Interface window if it is minimized. If raised, the window gains focus.
* @function Window.raiseMainWindow
* @deprecated Use {@link Window.raise|raise} instead.
*/
void raiseMainWindow();
/**jsdoc
* Display a dialog with the specified message and an "OK" button. The dialog is non-modal; the script continues without
* waiting for a user response.

View file

@ -172,11 +172,13 @@ void LoginDialog::openUrl(const QString& url) const {
offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) {
newObject->setProperty("url", url);
});
LoginDialog::hide();
} else {
if (!hmd->getShouldShowTablet() && !qApp->isHMDMode()) {
offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) {
newObject->setProperty("url", url);
});
LoginDialog::hide();
} else {
tablet->gotoWebScreen(url);
}

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