diff --git a/BUILD.md b/BUILD.md index 4a0274cea6..df3f18cf51 100644 --- a/BUILD.md +++ b/BUILD.md @@ -46,6 +46,7 @@ This can either be entered directly into your shell session before you build or The path it needs to be set to will depend on where and how Qt5 was installed. e.g. + export QT_CMAKE_PREFIX_PATH=/usr/local/Qt5.10.1/5.10.1/gcc_64/lib/cmake export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.10.1/clang_64/lib/cmake/ export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.10.1/lib/cmake export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 0daef5ae05..1ee3d2b7c8 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -6,13 +6,20 @@ Please read the [general build guide](BUILD.md) for information on dependencies Should you choose not to install Qt5 via a package manager that handles dependencies for you, you may be missing some Qt5 dependencies. On Ubuntu, for example, the following additional packages are required: - libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev + libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev zlib1g-dev -## Ubuntu 16.04 specific build guide +## Ubuntu 16.04/18.04 specific build guide + +### Ubuntu 18.04 only +Add the universe repository: +_(This is not enabled by default on the server edition)_ +```bash +sudo add-apt-repository universe +sudo apt-get update +``` ### Prepare environment -hifiqt5.10.1 -Install qt: +Install Qt 5.10.1: ```bash wget http://debian.highfidelity.com/pool/h/hi/hifiqt5.10.1_5.10.1_amd64.deb sudo dpkg -i hifiqt5.10.1_5.10.1_amd64.deb @@ -20,19 +27,20 @@ sudo dpkg -i hifiqt5.10.1_5.10.1_amd64.deb Install build dependencies: ```bash -sudo apt-get install libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev +sudo apt-get install libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev zlib1g-dev ``` To compile interface in a server you must install: ```bash -sudo apt -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 libxcomposite1 libxtst6 libxslt1.1 +sudo apt-get -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 libxcomposite1 libxtst6 libxslt1.1 ``` Install build tools: ```bash -sudo apt install cmake +sudo apt-get install cmake ``` + ### Get code and checkout the tag you need Clone this repository: @@ -48,12 +56,7 @@ git tags Then checkout last tag with: ```bash -git checkout tags/RELEASE-6819 -``` - -Or go to the highfidelity download page (https://highfidelity.com/download) to get the release version. For example, if there is a BETA 6731 type: -```bash -git checkout tags/RELEASE-6731 +git checkout tags/v0.71.0 ``` ### Compiling @@ -66,15 +69,20 @@ cd hifi/build Prepare makefiles: ```bash -cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.10.1/5.10/gcc_64/lib/cmake .. +cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.10.1/5.10.1/gcc_64/lib/cmake .. ``` -Start compilation and get a cup of coffee: +Start compilation of the server and get a cup of coffee: ```bash -make domain-server assignment-client interface +make domain-server assignment-client ``` -In a server does not make sense to compile interface +To compile interface: +```bash +make interface +``` + +In a server, it does not make sense to compile interface ### Running the software @@ -93,4 +101,4 @@ Running interface: ./interface/interface ``` -Go to localhost in running interface. +Go to localhost in the running interface. diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7255e1f295..b216819ed0 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ + @@ -75,6 +76,15 @@ android:enabled="true" android:exported="false" android:process=":breakpad_uploader"/> + + + + + + diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index ce5af01f29..6b44b2dc7a 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -355,5 +355,51 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_WebViewActivity_nativeProcessU AndroidHelper::instance().processURL(QString::fromUtf8(nativeString)); } +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_fragment_SettingsFragment_updateHifiSetting(JNIEnv *env, + jobject instance, + jstring group_, + jstring key_, + jboolean value_) { + const char *c_group = env->GetStringUTFChars(group_, 0); + const char *c_key = env->GetStringUTFChars(key_, 0); + + const QString group = QString::fromUtf8(c_group); + const QString key = QString::fromUtf8(c_key); + + env->ReleaseStringUTFChars(group_, c_group); + env->ReleaseStringUTFChars(key_, c_key); + + bool value = value_; + + Setting::Handle setting { QStringList() << group << key , !value }; + setting.set(value); +} + +JNIEXPORT jboolean JNICALL +Java_io_highfidelity_hifiinterface_fragment_SettingsFragment_getHifiSettingBoolean(JNIEnv *env, + jobject instance, + jstring group_, + jstring key_, + jboolean defaultValue) { + const char *c_group = env->GetStringUTFChars(group_, 0); + const char *c_key = env->GetStringUTFChars(key_, 0); + + const QString group = QString::fromUtf8(c_group); + const QString key = QString::fromUtf8(c_key); + + env->ReleaseStringUTFChars(group_, c_group); + env->ReleaseStringUTFChars(key_, c_key); + + Setting::Handle setting { QStringList() << group << key , defaultValue}; + return setting.get(); +} + +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_receiver_HeadsetStateReceiver_notifyHeadsetOn(JNIEnv *env, + jobject instance, + jboolean pluggedIn) { + AndroidHelper::instance().notifyHeadsetOn(pluggedIn); +} } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index f161783d6a..08e66a2f42 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -13,6 +13,7 @@ package io.highfidelity.hifiinterface; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -40,6 +41,7 @@ import java.util.HashMap; import java.util.List; import io.highfidelity.hifiinterface.fragment.WebViewFragment; +import io.highfidelity.hifiinterface.receiver.HeadsetStateReceiver; /*import com.google.vr.cardboard.DisplaySynchronizer; import com.google.vr.cardboard.DisplayUtils; @@ -55,6 +57,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW private static final int NORMAL_DPI = 160; private Vibrator mVibrator; + private HeadsetStateReceiver headsetStateReceiver; //public static native void handleHifiURL(String hifiURLString); private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager); @@ -151,6 +154,8 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW layoutParams.resolveLayoutDirection(View.LAYOUT_DIRECTION_RTL); qtLayout.addView(webSlidingDrawer, layoutParams); webSlidingDrawer.setVisibility(View.GONE); + + headsetStateReceiver = new HeadsetStateReceiver(); } @Override @@ -161,6 +166,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW } else { nativeEnterBackground(); } + unregisterReceiver(headsetStateReceiver); //gvrApi.pauseTracking(); } @@ -183,6 +189,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW nativeEnterForeground(); surfacesWorkaround(); keepInterfaceRunning = false; + registerReceiver(headsetStateReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); //gvrApi.resumeTracking(); } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index db6f0fca24..4c6d05a3e8 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -33,6 +33,7 @@ import io.highfidelity.hifiinterface.fragment.FriendsFragment; import io.highfidelity.hifiinterface.fragment.HomeFragment; import io.highfidelity.hifiinterface.fragment.LoginFragment; import io.highfidelity.hifiinterface.fragment.PolicyFragment; +import io.highfidelity.hifiinterface.fragment.SettingsFragment; import io.highfidelity.hifiinterface.task.DownloadProfileImageTask; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, @@ -80,6 +81,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people); + updateDebugMenu(mNavigationView.getMenu()); + Toolbar toolbar = findViewById(R.id.toolbar); toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle); setSupportActionBar(toolbar); @@ -108,6 +111,16 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On } } + private void updateDebugMenu(Menu menu) { + if (BuildConfig.DEBUG) { + for (int i=0; i < menu.size(); i++) { + if (menu.getItem(i).getItemId() == R.id.action_debug_settings) { + menu.getItem(i).setVisible(true); + } + } + } + } + private void loadFragment(String fragment) { switch (fragment) { case "Login": @@ -151,6 +164,13 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true); } + private void loadSettingsFragment() { + SettingsFragment fragment = SettingsFragment.newInstance(); + + loadFragment(fragment, getString(R.string.settings), getString(R.string.tagSettings), true); + } + + private void loadFragment(Fragment fragment, String title, String tag, boolean addToBackStack) { FragmentManager fragmentManager = getFragmentManager(); @@ -241,6 +261,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On case R.id.action_people: loadPeopleFragment(); return true; + case R.id.action_debug_settings: + loadSettingsFragment(); + return true; } return false; } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java index 2a008d7950..2475c4d887 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java @@ -98,13 +98,17 @@ public class FriendsFragment extends Fragment { mUsersAdapter.setListener(new UserListAdapter.AdapterListener() { @Override - public void onEmptyAdapter() { - mSwipeRefreshLayout.setRefreshing(false); + public void onEmptyAdapter(boolean shouldStopRefreshing) { + if (shouldStopRefreshing) { + mSwipeRefreshLayout.setRefreshing(false); + } } @Override - public void onNonEmptyAdapter() { - mSwipeRefreshLayout.setRefreshing(false); + public void onNonEmptyAdapter(boolean shouldStopRefreshing) { + if (shouldStopRefreshing) { + mSwipeRefreshLayout.setRefreshing(false); + } } @Override @@ -115,6 +119,8 @@ public class FriendsFragment extends Fragment { mUsersView.setAdapter(mUsersAdapter); + mUsersAdapter.startLoad(); + mSlidingUpPanelLayout.setFadeOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java index 7bd373cf1d..86b8625cfe 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java @@ -76,18 +76,22 @@ public class HomeFragment extends Fragment { }); mDomainAdapter.setListener(new DomainAdapter.AdapterListener() { @Override - public void onEmptyAdapter() { + public void onEmptyAdapter(boolean shouldStopRefreshing) { searchNoResultsView.setText(R.string.search_no_results); searchNoResultsView.setVisibility(View.VISIBLE); mDomainsView.setVisibility(View.GONE); - mSwipeRefreshLayout.setRefreshing(false); + if (shouldStopRefreshing) { + mSwipeRefreshLayout.setRefreshing(false); + } } @Override - public void onNonEmptyAdapter() { + public void onNonEmptyAdapter(boolean shouldStopRefreshing) { searchNoResultsView.setVisibility(View.GONE); mDomainsView.setVisibility(View.VISIBLE); - mSwipeRefreshLayout.setRefreshing(false); + if (shouldStopRefreshing) { + mSwipeRefreshLayout.setRefreshing(false); + } } @Override @@ -96,11 +100,20 @@ public class HomeFragment extends Fragment { } }); mDomainsView.setAdapter(mDomainAdapter); + mDomainAdapter.startLoad(); mSearchView = rootView.findViewById(R.id.searchView); mSearchIconView = rootView.findViewById(R.id.search_mag_icon); mClearSearch = rootView.findViewById(R.id.search_clear); + getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + + return rootView; + } + + @Override + public void onStart() { + super.onStart(); mSearchView.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} @@ -142,10 +155,6 @@ public class HomeFragment extends Fragment { mDomainAdapter.loadDomains(mSearchView.getText().toString(), true); } }); - - getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); - - return rootView; } @Override diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java new file mode 100644 index 0000000000..cc23665e72 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java @@ -0,0 +1,63 @@ +package io.highfidelity.hifiinterface.fragment; + +import android.content.SharedPreferences; +import android.media.audiofx.AcousticEchoCanceler; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.support.annotation.Nullable; + +import io.highfidelity.hifiinterface.R; + +public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { + + public native void updateHifiSetting(String group, String key, boolean value); + public native boolean getHifiSettingBoolean(String group, String key, boolean defaultValue); + + private final String HIFI_SETTINGS_ANDROID_GROUP = "Android"; + private final String HIFI_SETTINGS_AEC_KEY = "aec"; + private final String PREFERENCE_KEY_AEC = "aec"; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings); + + if (!AcousticEchoCanceler.isAvailable()) { + getPreferenceScreen().getPreferenceManager().findPreference("aec").setEnabled(false); + } + + getPreferenceScreen().getSharedPreferences().edit().putBoolean(PREFERENCE_KEY_AEC, + getHifiSettingBoolean(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, false)); + } + + public static SettingsFragment newInstance() { + SettingsFragment fragment = new SettingsFragment(); + return fragment; + } + + @Override + public void onResume() { + super.onResume(); + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onPause() { + super.onPause(); + getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + Preference pref = findPreference(key); + switch (key) { + case "aec": + updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, sharedPreferences.getBoolean(key, false)); + break; + default: + break; + } + } +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java new file mode 100644 index 0000000000..5645912d73 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java @@ -0,0 +1,18 @@ +package io.highfidelity.hifiinterface.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.util.Log; + +public class HeadsetStateReceiver extends BroadcastReceiver { + + private native void notifyHeadsetOn(boolean pluggedIn); + + @Override + public void onReceive(Context context, Intent intent) { + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + notifyHeadsetOn(audioManager.isWiredHeadsetOn()); + } +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java index 71d634e9ea..78251ac4a4 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java @@ -12,6 +12,7 @@ import android.widget.TextView; import com.squareup.picasso.Picasso; +import java.util.Arrays; import java.util.List; import io.highfidelity.hifiinterface.R; @@ -36,19 +37,41 @@ public class DomainAdapter extends RecyclerView.Adapter 0) { + mDomains = Arrays.copyOf(DOMAINS_TMP_CACHE, DOMAINS_TMP_CACHE.length); + notifyDataSetChanged(); + if (mAdapterListener != null) { + if (mDomains.length == 0) { + mAdapterListener.onEmptyAdapter(false); + } else { + mAdapterListener.onNonEmptyAdapter(false); + } + } + } + } + } + public void loadDomains(String filterText, boolean forceRefresh) { domainProvider.retrieve(filterText, new DomainProvider.DomainCallback() { @Override @@ -60,13 +83,18 @@ public class DomainAdapter extends RecyclerView.Adapter USERS_TMP_CACHE; + public UserListAdapter(Context c, UsersProvider usersProvider) { mContext = c; mInflater = LayoutInflater.from(mContext); mProvider = usersProvider; - loadUsers(); } public void setListener(AdapterListener adapterListener) { mAdapterListener = adapterListener; } + public void startLoad() { + useTmpCachedUsers(); + loadUsers(); + } + + private void useTmpCachedUsers() { + synchronized (this) { + if (USERS_TMP_CACHE != null && USERS_TMP_CACHE.size() > 0) { + mUsers = new ArrayList<>(USERS_TMP_CACHE.size()); + mUsers.addAll(USERS_TMP_CACHE); + notifyDataSetChanged(); + if (mAdapterListener != null) { + if (mUsers.isEmpty()) { + mAdapterListener.onEmptyAdapter(false); + } else { + mAdapterListener.onNonEmptyAdapter(false); + } + } + } + } + } + public void loadUsers() { mProvider.retrieve(new UsersProvider.UsersCallback() { @Override public void retrieveOk(List users) { mUsers = new ArrayList<>(users); notifyDataSetChanged(); - if (mAdapterListener != null) { - if (mUsers.isEmpty()) { - mAdapterListener.onEmptyAdapter(); - } else { - mAdapterListener.onNonEmptyAdapter(); + + synchronized (this) { + USERS_TMP_CACHE = new ArrayList<>(mUsers.size()); + USERS_TMP_CACHE.addAll(mUsers); + + if (mAdapterListener != null) { + if (mUsers.isEmpty()) { + mAdapterListener.onEmptyAdapter(true); + } else { + mAdapterListener.onNonEmptyAdapter(true); + } } } } @@ -240,8 +269,9 @@ public class UserListAdapter extends RecyclerView.Adapter + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index b158aba59d..abde15f484 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -29,4 +29,9 @@ tagFragmentLogin tagFragmentPolicy tagFragmentPeople + tagSettings + Settings + AEC + Acoustic Echo Cancellation + Developer diff --git a/android/app/src/main/res/xml/settings.xml b/android/app/src/main/res/xml/settings.xml new file mode 100644 index 0000000000..5ec47b1aff --- /dev/null +++ b/android/app/src/main/res/xml/settings.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index a6de0d469c..aa7aa399b2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -72,17 +72,17 @@ def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a') def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/' def breakpadDumpSymsDir = new File("${appDir}/build/tmp/breakpadDumpSyms") -def qtFile='qt-5.11.1_linux_armv8-libcpp_openssl.tgz' -def qtChecksum='f312c47cd8b8dbca824c32af4eec5e66' -def qtVersionId='nyCGcb91S4QbYeJhUkawO5x1lrLdSNB_' +def qtFile='qt-5.11.1_linux_armv8-libcpp_openssl_patched.tgz' +def qtChecksum='aa449d4bfa963f3bc9a9dfe558ba29df' +def qtVersionId='3S97HBM5G5Xw9EfE52sikmgdN3t6C2MN' if (Os.isFamily(Os.FAMILY_MAC)) { - qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl.tgz' - qtChecksum='a0c8b394aec5b0fcd46714ca3a53278a' - qtVersionId='QNa.lwNJaPc0eGuIL.xZ8ebeTuLL7rh8' + qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz' + qtChecksum='c83cc477c08a892e00c71764dca051a0' + qtVersionId='OxBD7iKINv1HbyOXmAmDrBb8AF3N.Kup' } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { - qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl.tgz' - qtChecksum='d80aed4233ce9e222aae8376e7a94bf9' - qtVersionId='iDVXu0i3WEXRFIxQCtzcJ2XuKrE8RIqB' + qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz' + qtChecksum='0582191cc55431aa4f660848a542883e' + qtVersionId='JfWM0P_Mz5Qp0LwpzhrsRwN3fqlLSFeT' } def packages = [ diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 2f03f15da7..639e9f924b 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -96,7 +96,6 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -177,6 +176,8 @@ void Agent::run() { // Create ScriptEngines on threaded-assignment thread then move to main thread. DependencyManager::set(ScriptEngine::AGENT_SCRIPT)->moveToThread(qApp->thread()); + DependencyManager::set(); + // make sure we request our script once the agent connects to the domain auto nodeList = DependencyManager::get(); @@ -360,154 +361,178 @@ void Agent::scriptRequestFinished() { } void Agent::executeScript() { - _scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload); + // the following block is scoped so that any shared pointers we take here + // are cleared before we call setFinished at the end of the function + { + _scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload); - // setup an Avatar for the script to use - auto scriptedAvatar = DependencyManager::get(); + // setup an Avatar for the script to use + auto scriptedAvatar = DependencyManager::get(); - scriptedAvatar->setID(getSessionUUID()); + scriptedAvatar->setID(getSessionUUID()); - connect(_scriptEngine.data(), SIGNAL(update(float)), - scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection); - scriptedAvatar->setForceFaceTrackerConnected(true); + connect(_scriptEngine.data(), SIGNAL(update(float)), + scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection); + scriptedAvatar->setForceFaceTrackerConnected(true); - // call model URL setters with empty URLs so our avatar, if user, will have the default models - scriptedAvatar->setSkeletonModelURL(QUrl()); + // call model URL setters with empty URLs so our avatar, if user, will have the default models + scriptedAvatar->setSkeletonModelURL(QUrl()); - // force lazy initialization of the head data for the scripted avatar - // since it is referenced below by computeLoudness and getAudioLoudness - scriptedAvatar->getHeadOrientation(); + // force lazy initialization of the head data for the scripted avatar + // since it is referenced below by computeLoudness and getAudioLoudness + scriptedAvatar->getHeadOrientation(); - // give this AvatarData object to the script engine - _scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data()); + // give this AvatarData object to the script engine + _scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data()); - // give scripts access to the Users object - _scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); + // give scripts access to the Users object + _scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); - auto player = DependencyManager::get(); - connect(player.data(), &recording::Deck::playbackStateChanged, [=] { - if (player->isPlaying()) { - auto recordingInterface = DependencyManager::get(); - if (recordingInterface->getPlayFromCurrentLocation()) { - scriptedAvatar->setRecordingBasis(); + auto player = DependencyManager::get(); + connect(player.data(), &recording::Deck::playbackStateChanged, [&player, &scriptedAvatar] { + if (player->isPlaying()) { + auto recordingInterface = DependencyManager::get(); + if (recordingInterface->getPlayFromCurrentLocation()) { + scriptedAvatar->setRecordingBasis(); + } + + // these procedural movements are included in the recordings + scriptedAvatar->setHasProceduralEyeFaceMovement(false); + scriptedAvatar->setHasProceduralBlinkFaceMovement(false); + scriptedAvatar->setHasAudioEnabledFaceMovement(false); + } else { + scriptedAvatar->clearRecordingBasis(); + + // restore procedural blendshape movement + scriptedAvatar->setHasProceduralEyeFaceMovement(true); + scriptedAvatar->setHasProceduralBlinkFaceMovement(true); + scriptedAvatar->setHasAudioEnabledFaceMovement(true); } - } else { - scriptedAvatar->clearRecordingBasis(); - } - }); + }); - using namespace recording; - static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME); - Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [scriptedAvatar](Frame::ConstPointer frame) { + using namespace recording; + static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME); + Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [scriptedAvatar](Frame::ConstPointer frame) { + + auto recordingInterface = DependencyManager::get(); + bool useFrameSkeleton = recordingInterface->getPlayerUseSkeletonModel(); + + // FIXME - the ability to switch the avatar URL is not actually supported when playing back from a recording + if (!useFrameSkeleton) { + static std::once_flag warning; + std::call_once(warning, [] { + qWarning() << "Recording.setPlayerUseSkeletonModel(false) is not currently supported."; + }); + } + + AvatarData::fromFrame(frame->data, *scriptedAvatar); + }); + + using namespace recording; + static const FrameType AUDIO_FRAME_TYPE = Frame::registerFrameType(AudioConstants::getAudioFrameName()); + Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this, &scriptedAvatar](Frame::ConstPointer frame) { + static quint16 audioSequenceNumber{ 0 }; + + QByteArray audio(frame->data); + + if (_isNoiseGateEnabled) { + int16_t* samples = reinterpret_cast(audio.data()); + int numSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; + _audioGate.render(samples, samples, numSamples); + } + + computeLoudness(&audio, scriptedAvatar); + + // state machine to detect gate opening and closing + bool audioGateOpen = (scriptedAvatar->getAudioLoudness() != 0.0f); + bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened + bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed + _audioGateOpen = audioGateOpen; + Q_UNUSED(openedInLastBlock); + + // the codec must be flushed to silence before sending silent packets, + // so delay the transition to silent packets by one packet after becoming silent. + auto packetType = PacketType::MicrophoneAudioNoEcho; + if (!audioGateOpen && !closedInLastBlock) { + packetType = PacketType::SilentAudioFrame; + } + + Transform audioTransform; + auto headOrientation = scriptedAvatar->getHeadOrientation(); + audioTransform.setTranslation(scriptedAvatar->getWorldPosition()); + audioTransform.setRotation(headOrientation); + + QByteArray encodedBuffer; + if (_encoder) { + _encoder->encode(audio, encodedBuffer); + } else { + encodedBuffer = audio; + } + + AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, false, + audioTransform, scriptedAvatar->getWorldPosition(), glm::vec3(0), + packetType, _selectedCodecName); + }); + + auto avatarHashMap = DependencyManager::set(); + _scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data()); + + // register ourselves to the script engine + _scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this)); + + _scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); + _scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); + + QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor); + _scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); + + auto entityScriptingInterface = DependencyManager::get(); + + _scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer); + + _scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, + LocationScriptingInterface::locationSetter); auto recordingInterface = DependencyManager::get(); - bool useFrameSkeleton = recordingInterface->getPlayerUseSkeletonModel(); + _scriptEngine->registerGlobalObject("Recording", recordingInterface.data()); - // FIXME - the ability to switch the avatar URL is not actually supported when playing back from a recording - if (!useFrameSkeleton) { - static std::once_flag warning; - std::call_once(warning, [] { - qWarning() << "Recording.setPlayerUseSkeletonModel(false) is not currently supported."; - }); + entityScriptingInterface->init(); + + _entityViewer.init(); + + entityScriptingInterface->setEntityTree(_entityViewer.getTree()); + + DependencyManager::set(_entityViewer.getTree()); + + _avatarAudioTimer.start(); + + // Agents should run at 45hz + static const int AVATAR_DATA_HZ = 45; + static const int AVATAR_DATA_IN_MSECS = MSECS_PER_SECOND / AVATAR_DATA_HZ; + QTimer* avatarDataTimer = new QTimer(this); + connect(avatarDataTimer, &QTimer::timeout, this, &Agent::processAgentAvatar); + avatarDataTimer->setSingleShot(false); + avatarDataTimer->setInterval(AVATAR_DATA_IN_MSECS); + avatarDataTimer->setTimerType(Qt::PreciseTimer); + avatarDataTimer->start(); + + _scriptEngine->run(); + + Frame::clearFrameHandler(AUDIO_FRAME_TYPE); + Frame::clearFrameHandler(AVATAR_FRAME_TYPE); + + if (recordingInterface->isPlaying()) { + recordingInterface->stopPlaying(); } - AvatarData::fromFrame(frame->data, *scriptedAvatar); - }); - - using namespace recording; - static const FrameType AUDIO_FRAME_TYPE = Frame::registerFrameType(AudioConstants::getAudioFrameName()); - Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this, &scriptedAvatar](Frame::ConstPointer frame) { - static quint16 audioSequenceNumber{ 0 }; - - QByteArray audio(frame->data); - - if (_isNoiseGateEnabled) { - int16_t* samples = reinterpret_cast(audio.data()); - int numSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; - _audioGate.render(samples, samples, numSamples); + if (recordingInterface->isRecording()) { + recordingInterface->stopRecording(); } - computeLoudness(&audio, scriptedAvatar); + avatarDataTimer->stop(); + _avatarAudioTimer.stop(); + } - // state machine to detect gate opening and closing - bool audioGateOpen = (scriptedAvatar->getAudioLoudness() != 0.0f); - bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened - bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed - _audioGateOpen = audioGateOpen; - Q_UNUSED(openedInLastBlock); - - // the codec must be flushed to silence before sending silent packets, - // so delay the transition to silent packets by one packet after becoming silent. - auto packetType = PacketType::MicrophoneAudioNoEcho; - if (!audioGateOpen && !closedInLastBlock) { - packetType = PacketType::SilentAudioFrame; - } - - Transform audioTransform; - auto headOrientation = scriptedAvatar->getHeadOrientation(); - audioTransform.setTranslation(scriptedAvatar->getWorldPosition()); - audioTransform.setRotation(headOrientation); - - QByteArray encodedBuffer; - if (_encoder) { - _encoder->encode(audio, encodedBuffer); - } else { - encodedBuffer = audio; - } - - AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, false, - audioTransform, scriptedAvatar->getWorldPosition(), glm::vec3(0), - packetType, _selectedCodecName); - }); - - auto avatarHashMap = DependencyManager::set(); - _scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data()); - - // register ourselves to the script engine - _scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this)); - - _scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); - _scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); - - QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor); - _scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); - - auto entityScriptingInterface = DependencyManager::get(); - - _scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer); - - _scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, - LocationScriptingInterface::locationSetter); - - auto recordingInterface = DependencyManager::get(); - _scriptEngine->registerGlobalObject("Recording", recordingInterface.data()); - - entityScriptingInterface->init(); - - _entityViewer.init(); - - entityScriptingInterface->setEntityTree(_entityViewer.getTree()); - - DependencyManager::set(_entityViewer.getTree()); - - QMetaObject::invokeMethod(&_avatarAudioTimer, "start"); - - // Agents should run at 45hz - static const int AVATAR_DATA_HZ = 45; - static const int AVATAR_DATA_IN_MSECS = MSECS_PER_SECOND / AVATAR_DATA_HZ; - QTimer* avatarDataTimer = new QTimer(this); - connect(avatarDataTimer, &QTimer::timeout, this, &Agent::processAgentAvatar); - avatarDataTimer->setSingleShot(false); - avatarDataTimer->setInterval(AVATAR_DATA_IN_MSECS); - avatarDataTimer->setTimerType(Qt::PreciseTimer); - avatarDataTimer->start(); - - _scriptEngine->run(); - - Frame::clearFrameHandler(AUDIO_FRAME_TYPE); - Frame::clearFrameHandler(AVATAR_FRAME_TYPE); - - DependencyManager::destroy(); setFinished(true); } @@ -859,17 +884,25 @@ void Agent::aboutToFinish() { DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - DependencyManager::destroy(); + + // drop our shared pointer to the script engine, then ask ScriptEngines to shutdown scripting + // this ensures that the ScriptEngine goes down before ScriptEngines + _scriptEngine.clear(); + + { + DependencyManager::get()->shutdownScripting(); + } + + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); - // cleanup codec & encoder if (_codec && _encoder) { _codec->releaseEncoder(_encoder); diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index edbba20dc7..561afee296 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -654,6 +654,15 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer if (addToIgnore) { senderNode->addIgnoredNode(ignoredUUID); + + if (ignoredNode) { + // send a reliable kill packet to remove the sending avatar for the ignored avatar + auto killPacket = NLPacket::create(PacketType::KillAvatar, + NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); + killPacket->write(senderNode->getUUID().toRfc4122()); + killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected); + nodeList->sendPacket(std::move(killPacket), *ignoredNode); + } } else { senderNode->removeIgnoredNode(ignoredUUID); } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index f524c071ec..6c01e6e02b 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -11,6 +11,7 @@ #include "AvatarMixerClientData.h" +#include #include #include @@ -218,6 +219,10 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node } void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointer other) { + ignoreOther(self.data(), other.data()); +} + +void AvatarMixerClientData::ignoreOther(const Node* self, const Node* other) { if (!isRadiusIgnoring(other->getUUID())) { addToRadiusIgnoringSet(other->getUUID()); auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); @@ -235,9 +240,20 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe } } -void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other) { - if (isRadiusIgnoring(other)) { - _radiusIgnoredOthers.erase(other); +bool AvatarMixerClientData::isRadiusIgnoring(const QUuid& other) const { + return std::find(_radiusIgnoredOthers.cbegin(), _radiusIgnoredOthers.cend(), other) != _radiusIgnoredOthers.cend(); +} + +void AvatarMixerClientData::addToRadiusIgnoringSet(const QUuid& other) { + if (!isRadiusIgnoring(other)) { + _radiusIgnoredOthers.push_back(other); + } +} + +void AvatarMixerClientData::removeFromRadiusIgnoringSet(const QUuid& other) { + auto ignoredOtherIter = std::find(_radiusIgnoredOthers.cbegin(), _radiusIgnoredOthers.cend(), other); + if (ignoredOtherIter != _radiusIgnoredOthers.cend()) { + _radiusIgnoredOthers.erase(ignoredOtherIter); } } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index a892455fe3..d38a90ef1f 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include @@ -45,6 +45,7 @@ public: int parseData(ReceivedMessage& message) override; AvatarData& getAvatar() { return *_avatar; } + const AvatarData& getAvatar() const { return *_avatar; } const AvatarData* getConstAvatarData() const { return _avatar.get(); } AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; } @@ -90,11 +91,11 @@ public: void loadJSONStats(QJsonObject& jsonObject) const; glm::vec3 getPosition() const { return _avatar ? _avatar->getWorldPosition() : glm::vec3(0); } - glm::vec3 getGlobalBoundingBoxCorner() const { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); } - bool isRadiusIgnoring(const QUuid& other) const { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); } - void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); } - void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other); + bool isRadiusIgnoring(const QUuid& other) const; + void addToRadiusIgnoringSet(const QUuid& other); + void removeFromRadiusIgnoringSet(const QUuid& other); void ignoreOther(SharedNodePointer self, SharedNodePointer other); + void ignoreOther(const Node* self, const Node* other); void readViewFrustumPacket(const QByteArray& message); @@ -166,7 +167,7 @@ private: int _numOutOfOrderSends = 0; SimpleMovingAverage _avgOtherAvatarDataRate; - std::unordered_set _radiusIgnoredOthers; + std::vector _radiusIgnoredOthers; ConicalViewFrustums _currentViewFrustums; int _recentOtherAvatarsInView { 0 }; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index f347ff1f10..7368db0c31 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -33,6 +34,8 @@ #include "AvatarMixer.h" #include "AvatarMixerClientData.h" +namespace chrono = std::chrono; + void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) { _begin = begin; _end = end; @@ -209,7 +212,18 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { _stats.jobElapsedTime += (end - start); } +AABox computeBubbleBox(const AvatarData& avatar, float bubbleExpansionFactor) { + AABox box = avatar.getGlobalBoundingBox(); + glm::vec3 scale = box.getScale(); + scale *= bubbleExpansionFactor; + const glm::vec3 MIN_BUBBLE_SCALE(0.3f, 1.3f, 0.3); + scale = glm::max(scale, MIN_BUBBLE_SCALE); + box.setScaleStayCentered(glm::max(scale, MIN_BUBBLE_SCALE)); + return box; +} + void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { + const Node* destinationNode = node.data(); auto nodeList = DependencyManager::get(); @@ -220,7 +234,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) _stats.nodesBroadcastedTo++; - AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + AvatarMixerClientData* nodeData = reinterpret_cast(destinationNode->getLinkedData()); nodeData->resetInViewStats(); @@ -242,12 +256,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int traitBytesSent = 0; // max number of avatarBytes per frame - auto maxAvatarBytesPerFrame = (_maxKbpsPerNode * BYTES_PER_KILOBIT) / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND; + int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND); - // FIXME - find a way to not send the sessionID for every avatar - int minimumBytesPerAvatar = AvatarDataPacket::AVATAR_HAS_FLAGS_SIZE + NUM_BYTES_RFC4122_UUID; - - int overBudgetAvatars = 0; // keep track of the number of other avatars held back in this frame int numAvatarsHeldBack = 0; @@ -260,66 +270,38 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) bool PALIsOpen = nodeData->getRequestsDomainListData(); // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them - bool getsAnyIgnored = PALIsOpen && node->getCanKick(); + bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick(); - if (PALIsOpen) { - // Increase minimumBytesPerAvatar if the PAL is open - minimumBytesPerAvatar += sizeof(AvatarDataPacket::AvatarGlobalPosition) + - sizeof(AvatarDataPacket::AudioLoudness); - } + // Bandwidth allowance for data that must be sent. + int minimumBytesPerAvatar = PALIsOpen ? AvatarDataPacket::AVATAR_HAS_FLAGS_SIZE + NUM_BYTES_RFC4122_UUID + + sizeof(AvatarDataPacket::AvatarGlobalPosition) + sizeof(AvatarDataPacket::AudioLoudness) : 0; // setup a PacketList for the avatarPackets auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); + static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; - // Define the minimum bubble size - static const glm::vec3 minBubbleSize = avatar.getSensorToWorldScale() * glm::vec3(0.3f, 1.3f, 0.3f); - // Define the scale of the box for the current node - glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f * avatar.getSensorToWorldScale(); - // Set up the bounding box for the current node - AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale); - // Clamp the size of the bounding box to a minimum scale - if (glm::any(glm::lessThan(nodeBoxScale, minBubbleSize))) { - nodeBox.setScaleStayCentered(minBubbleSize); - } - // Quadruple the scale of both bounding boxes - nodeBox.embiggen(4.0f); - - - // setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes - std::vector avatarsToSort; - std::unordered_map avatarDataToNodes; - std::unordered_map avatarEncodeTimes; - std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { - // make sure this is an agent that we have avatar data for before considering it for inclusion - if (otherNode->getType() == NodeType::Agent - && otherNode->getLinkedData()) { - const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); - - AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer(); - avatarsToSort.push_back(otherAvatar); - avatarDataToNodes[otherAvatar] = otherNode; - QUuid id = otherAvatar->getSessionUUID(); - avatarEncodeTimes[id] = nodeData->getLastOtherAvatarEncodeTime(id); - } - }); + // compute node bounding box + const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically + AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR); class SortableAvatar: public PrioritySortUtil::Sortable { public: SortableAvatar() = delete; - SortableAvatar(const AvatarSharedPointer& avatar, uint64_t lastEncodeTime) - : _avatar(avatar), _lastEncodeTime(lastEncodeTime) {} - glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } + SortableAvatar(const AvatarData* avatar, const Node* avatarNode, uint64_t lastEncodeTime) + : _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {} + glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); } float getRadius() const override { - glm::vec3 nodeBoxHalfScale = (_avatar->getWorldPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale()); - return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); + glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale(); + return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z)); } uint64_t getTimestamp() const override { return _lastEncodeTime; } - AvatarSharedPointer getAvatar() const { return _avatar; } + const Node* getNode() const { return _node; } private: - AvatarSharedPointer _avatar; + const AvatarData* _avatar; + const Node* _node; uint64_t _lastEncodeTime; }; @@ -329,15 +311,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge); + sortedAvatars.reserve(_end - _begin); - // ignore or sort - const AvatarSharedPointer& thisAvatar = nodeData->getAvatarSharedPointer(); - for (const auto& avatar : avatarsToSort) { - if (avatar == thisAvatar) { - // don't echo updates to self + for (auto listedNode = _begin; listedNode != _end; ++listedNode) { + Node* otherNodeRaw = (*listedNode).data(); + if (otherNodeRaw->getType() != NodeType::Agent + || !otherNodeRaw->getLinkedData() + || otherNodeRaw == destinationNode) { continue; } + auto avatarNode = otherNodeRaw; + bool shouldIgnore = false; // We ignore other nodes for a couple of reasons: // 1) ignore bubbles and ignore specific node @@ -345,53 +330,39 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // happen if for example the avatar is connected on a desktop and sending // updates at ~30hz. So every 3 frames we skip a frame. - auto avatarNode = avatarDataToNodes[avatar]; assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map - const AvatarMixerClientData* avatarNodeData = reinterpret_cast(avatarNode->getLinkedData()); - assert(avatarNodeData); // we can't have gotten here without avatarNode having valid data + const AvatarMixerClientData* avatarClientNodeData = reinterpret_cast(avatarNode->getLinkedData()); + assert(avatarClientNodeData); // we can't have gotten here without avatarNode having valid data quint64 startIgnoreCalculation = usecTimestampNow(); // make sure we have data for this avatar, that it isn't the same node, // and isn't an avatar that the viewing node has ignored // or that has ignored the viewing node - if (!avatarNode->getLinkedData() - || avatarNode->getUUID() == node->getUUID() - || (node->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) - || (avatarNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) { + if ((destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) + || (avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) { shouldIgnore = true; } else { // Check to see if the space bubble is enabled // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored - if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { - float sensorToWorldScale = avatarNodeData->getAvatarSharedPointer()->getSensorToWorldScale(); - // Define the scale of the box for the current other node - glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f * sensorToWorldScale; - // Set up the bounding box for the current other node - AABox otherNodeBox(avatarNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - // Clamp the size of the bounding box to a minimum scale - if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) { - otherNodeBox.setScaleStayCentered(minBubbleSize); - } - // Change the scale of both bounding boxes - // (This is an arbitrary number determined empirically) - otherNodeBox.embiggen(2.4f); - + if (destinationNode->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { // Perform the collision check between the two bounding boxes + const float OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR = 2.4f; // magic number determined empirically + AABox otherNodeBox = computeBubbleBox(avatarClientNodeData->getAvatar(), OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR); if (nodeBox.touches(otherNodeBox)) { - nodeData->ignoreOther(node, avatarNode); + nodeData->ignoreOther(destinationNode, avatarNode); shouldIgnore = !getsAnyIgnored; } } // Not close enough to ignore if (!shouldIgnore) { - nodeData->removeFromRadiusIgnoringSet(node, avatarNode->getUUID()); + nodeData->removeFromRadiusIgnoringSet(avatarNode->getUUID()); } } if (!shouldIgnore) { AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID()); - AvatarDataSequenceNumber lastSeqFromSender = avatarNodeData->getLastReceivedSequenceNumber(); + AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber(); // FIXME - This code does appear to be working. But it seems brittle. // It supports determining if the frame of data for this "other" @@ -416,12 +387,10 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (!shouldIgnore) { // sort this one for later - uint64_t lastEncodeTime = 0; - std::unordered_map::const_iterator itr = avatarEncodeTimes.find(avatar->getSessionUUID()); - if (itr != avatarEncodeTimes.end()) { - lastEncodeTime = itr->second; - } - sortedAvatars.push(SortableAvatar(avatar, lastEncodeTime)); + const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData(); + auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNodeData->getSessionUUID()); + + sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); } } @@ -429,19 +398,31 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); - while (!sortedAvatars.empty()) { - const auto avatarData = sortedAvatars.top().getAvatar(); - sortedAvatars.pop(); - remainingAvatars--; - auto otherNode = avatarDataToNodes[avatarData]; + const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); + for (const auto& sortedAvatar : sortedAvatarVector) { + const Node* otherNode = sortedAvatar.getNode(); + auto lastEncodeForOther = sortedAvatar.getTimestamp(); + assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map - // NOTE: Here's where we determine if we are over budget and drop to bare minimum data + AvatarData::AvatarDataDetail detail = AvatarData::NoData; + + // NOTE: Here's where we determine if we are over budget and drop remaining avatars, + // or send minimal avatar data in uncommon case of PALIsOpen. int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; bool overBudget = (identityBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes) > maxAvatarBytesPerFrame; + if (overBudget) { + if (PALIsOpen) { + _stats.overBudgetAvatars++; + detail = AvatarData::PALMinimum; + } else { + _stats.overBudgetAvatars += remainingAvatars; + break; + } + } - quint64 startAvatarDataPacking = usecTimestampNow(); + auto startAvatarDataPacking = chrono::high_resolution_clock::now(); ++numOtherAvatars; @@ -458,32 +439,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow()); } - // determine if avatar is in view which determines how much data to send - glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition(); - glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f * otherAvatar->getSensorToWorldScale(); - AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - bool isInView = nodeData->otherAvatarInView(otherNodeBox); + // Typically all out-of-view avatars but such avatars' priorities will rise with time: + bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; - // start a new segment in the PacketList for this avatar - avatarPacketList->startSegment(); - - AvatarData::AvatarDataDetail detail; - - if (overBudget) { - overBudgetAvatars++; - _stats.overBudgetAvatars++; - detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::NoData; - } else if (!isInView) { + if (isLowerPriority) { detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; nodeData->incrementAvatarOutOfView(); - } else { - detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO - ? AvatarData::SendAllData : AvatarData::CullSmallData; + } else if (!overBudget) { + detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; nodeData->incrementAvatarInView(); } bool includeThisAvatar = true; - auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); lastSentJointsForOther.resize(otherAvatar->getJointCount()); @@ -493,14 +460,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray bool dropFaceTracking = false; - quint64 start = usecTimestampNow(); + auto startSerialize = chrono::high_resolution_clock::now(); QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); - quint64 end = usecTimestampNow(); - _stats.toByteArrayElapsedTime += (end - start); + auto endSerialize = chrono::high_resolution_clock::now(); + _stats.toByteArrayElapsedTime += + (quint64) chrono::duration_cast(endSerialize - startSerialize).count(); - static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; if (bytes.size() > maxAvatarDataBytes) { qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() << "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data"; @@ -526,8 +493,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } if (includeThisAvatar) { + // start a new segment in the PacketList for this avatar + avatarPacketList->startSegment(); numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += avatarPacketList->write(bytes); + avatarPacketList->endSegment(); if (detail != AvatarData::NoData) { _stats.numOthersIncluded++; @@ -545,15 +515,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // It would be nice if we could tweak its future sort priority to put it at the back of the list. } - avatarPacketList->endSegment(); - - quint64 endAvatarDataPacking = usecTimestampNow(); - _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + auto endAvatarDataPacking = chrono::high_resolution_clock::now(); + _stats.avatarDataPackingElapsedTime += + (quint64) chrono::duration_cast(endAvatarDataPacking - startAvatarDataPacking).count(); // use helper to add any changed traits to our packet list traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); - - traitsPacketList->getDataSize(); + remainingAvatars--; } quint64 startPacketSending = usecTimestampNow(); @@ -565,7 +533,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) _stats.numBytesSent += numAvatarDataBytes; // send the avatar data PacketList - nodeList->sendPacketList(std::move(avatarPacketList), *node); + nodeList->sendPacketList(std::move(avatarPacketList), *destinationNode); // record the bytes sent for other avatar data in the AvatarMixerClientData nodeData->recordSentAvatarData(numAvatarDataBytes); @@ -575,7 +543,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (traitsPacketList->getNumPackets() >= 1) { // send the traits packet list - nodeList->sendPacketList(std::move(traitsPacketList), *node); + nodeList->sendPacketList(std::move(traitsPacketList), *destinationNode); } // record the number of avatars held back this frame diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 6f04cfa196..7d2b267a05 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -145,3 +145,15 @@ void ScriptableAvatar::update(float deltatime) { _clientTraitsHandler->sendChangedTraitsToMixer(); } + +void ScriptableAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) { + _headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement); +} + +void ScriptableAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) { + _headData->setHasProceduralEyeFaceMovement(hasProceduralEyeFaceMovement); +} + +void ScriptableAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { + _headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement); +} diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 89f9369133..52beba72a1 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -157,9 +157,16 @@ public: virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override; + void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement); + bool getHasProceduralBlinkFaceMovement() const override { return _headData->getHasProceduralBlinkFaceMovement(); } + void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement); + bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); } + void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); + bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); } + private slots: void update(float deltatime); - + private: AnimationPointer _animation; AnimationDetails _animationDetails; diff --git a/cmake/externals/serverless-content/CMakeLists.txt b/cmake/externals/serverless-content/CMakeLists.txt index b4cacd8f90..12e2b7a4c6 100644 --- a/cmake/externals/serverless-content/CMakeLists.txt +++ b/cmake/externals/serverless-content/CMakeLists.txt @@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC72.zip - URL_MD5 b1d8faf9266bfbff88274a484911eb99 + URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC73.zip + URL_MD5 0c5edfb63cafb042311d3cf25261fbf2 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index 1d1e5970d9..9c5ad70c78 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -9,7 +9,6 @@ # macro(AUTOSCRIBE_SHADER) - message(STATUS "Processing shader ${SHADER_FILE}") unset(SHADER_INCLUDE_FILES) # Grab include files foreach(includeFile ${ARGN}) diff --git a/cmake/macros/TargetJson.cmake b/cmake/macros/TargetJson.cmake index 057024cd0a..9262c2ce48 100644 --- a/cmake/macros/TargetJson.cmake +++ b/cmake/macros/TargetJson.cmake @@ -8,6 +8,5 @@ macro(TARGET_JSON) add_dependency_external_projects(json) find_package(JSON REQUIRED) - message("JSON_INCLUDE_DIRS ${JSON_INCLUDE_DIRS}") target_include_directories(${TARGET_NAME} PUBLIC ${JSON_INCLUDE_DIRS}) endmacro() \ No newline at end of file diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 2307b1be3b..e23d9e57a8 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -463,19 +463,15 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect limitedNodeList->eachNodeBreakable([nodeConnection, username, &existingNodeID](const SharedNodePointer& node){ if (node->getPublicSocket() == nodeConnection.publicSockAddr && node->getLocalSocket() == nodeConnection.localSockAddr) { - // we have a node that already has these exact sockets - this can occur if a node - // is failing to connect to the domain - - // we'll re-use the existing node ID - // as long as the user hasn't changed their username (by logging in or logging out) - auto existingNodeData = static_cast(node->getLinkedData()); - - if (existingNodeData->getUsername() == username) { - qDebug() << "Deleting existing connection from same sockaddr: " << node->getUUID(); - existingNodeID = node->getUUID(); - return false; - } + // we have a node that already has these exact sockets + // this can occur if a node is failing to connect to the domain + + // remove the old node before adding the new node + qDebug() << "Deleting existing connection from same sockaddr: " << node->getUUID(); + existingNodeID = node->getUUID(); + return false; } + return true; }); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 990d84a774..cc9fe8ef4c 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -332,6 +332,10 @@ if (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/resources/fonts" "${RESOURCES_DEV_DIR}/fonts" + # add redirect json to macOS builds. + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${PROJECT_SOURCE_DIR}/resources/serverless/redirect.json" + "${RESOURCES_DEV_DIR}/serverless/redirect.json" ) # call the fixup_interface macro to add required bundling commands for installation @@ -360,6 +364,9 @@ else() COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${PROJECT_SOURCE_DIR}/resources/serverless/tutorial.json" "${RESOURCES_DEV_DIR}/serverless/tutorial.json" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${PROJECT_SOURCE_DIR}/resources/serverless/redirect.json" + "${RESOURCES_DEV_DIR}/serverless/redirect.json" # copy JSDoc files beside the executable COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/tools/jsdoc/out" diff --git a/interface/resources/avatar/animations/idle.fbx b/interface/resources/avatar/animations/idle.fbx index 801237036f..88c79185a1 100644 Binary files a/interface/resources/avatar/animations/idle.fbx and b/interface/resources/avatar/animations/idle.fbx differ diff --git a/interface/resources/avatar/animations/jog_bwd.fbx b/interface/resources/avatar/animations/jog_bwd.fbx new file mode 100644 index 0000000000..479392d44c Binary files /dev/null and b/interface/resources/avatar/animations/jog_bwd.fbx differ diff --git a/interface/resources/avatar/animations/jump_in_air.fbx b/interface/resources/avatar/animations/jump_in_air.fbx index 72d8fc269c..2e6ad32f4f 100644 Binary files a/interface/resources/avatar/animations/jump_in_air.fbx and b/interface/resources/avatar/animations/jump_in_air.fbx differ diff --git a/interface/resources/avatar/animations/jump_land.fbx b/interface/resources/avatar/animations/jump_land.fbx index c0034bd091..f057c80085 100644 Binary files a/interface/resources/avatar/animations/jump_land.fbx and b/interface/resources/avatar/animations/jump_land.fbx differ diff --git a/interface/resources/avatar/animations/jump_running_launch_land.fbx b/interface/resources/avatar/animations/jump_running_launch_land.fbx new file mode 100644 index 0000000000..ad922d948e Binary files /dev/null and b/interface/resources/avatar/animations/jump_running_launch_land.fbx differ diff --git a/interface/resources/avatar/animations/jump_standing_apex.fbx b/interface/resources/avatar/animations/jump_standing_apex.fbx index 6385766b4a..53b79758aa 100644 Binary files a/interface/resources/avatar/animations/jump_standing_apex.fbx and b/interface/resources/avatar/animations/jump_standing_apex.fbx differ diff --git a/interface/resources/avatar/animations/jump_standing_land.fbx b/interface/resources/avatar/animations/jump_standing_land.fbx index fd97dae92a..aaeeb186fc 100644 Binary files a/interface/resources/avatar/animations/jump_standing_land.fbx and b/interface/resources/avatar/animations/jump_standing_land.fbx differ diff --git a/interface/resources/avatar/animations/jump_standing_land_settle.fbx b/interface/resources/avatar/animations/jump_standing_land_settle.fbx new file mode 100644 index 0000000000..1a11781e22 Binary files /dev/null and b/interface/resources/avatar/animations/jump_standing_land_settle.fbx differ diff --git a/interface/resources/avatar/animations/jump_standing_launch.fbx b/interface/resources/avatar/animations/jump_standing_launch.fbx new file mode 100644 index 0000000000..06479c8885 Binary files /dev/null and b/interface/resources/avatar/animations/jump_standing_launch.fbx differ diff --git a/interface/resources/avatar/animations/jump_takeoff.fbx b/interface/resources/avatar/animations/jump_takeoff.fbx index 4119b2417f..3976725676 100644 Binary files a/interface/resources/avatar/animations/jump_takeoff.fbx and b/interface/resources/avatar/animations/jump_takeoff.fbx differ diff --git a/interface/resources/avatar/animations/run_bwd.fbx b/interface/resources/avatar/animations/run_bwd.fbx new file mode 100644 index 0000000000..62f49c9729 Binary files /dev/null and b/interface/resources/avatar/animations/run_bwd.fbx differ diff --git a/interface/resources/avatar/animations/settle_to_idle.fbx b/interface/resources/avatar/animations/settle_to_idle.fbx index a5ccbfb490..fba4305e9a 100644 Binary files a/interface/resources/avatar/animations/settle_to_idle.fbx and b/interface/resources/avatar/animations/settle_to_idle.fbx differ diff --git a/interface/resources/avatar/animations/side_step_left.fbx b/interface/resources/avatar/animations/side_step_left.fbx index 4bface0c75..ef8cbe0a5c 100644 Binary files a/interface/resources/avatar/animations/side_step_left.fbx and b/interface/resources/avatar/animations/side_step_left.fbx differ diff --git a/interface/resources/avatar/animations/talk.fbx b/interface/resources/avatar/animations/talk.fbx index ba8f88589e..c29bf1e9bf 100644 Binary files a/interface/resources/avatar/animations/talk.fbx and b/interface/resources/avatar/animations/talk.fbx differ diff --git a/interface/resources/avatar/animations/walk_bwd_fast.fbx b/interface/resources/avatar/animations/walk_bwd_fast.fbx index f2007b03b1..f9a9ba0e26 100644 Binary files a/interface/resources/avatar/animations/walk_bwd_fast.fbx and b/interface/resources/avatar/animations/walk_bwd_fast.fbx differ diff --git a/interface/resources/avatar/animations/walk_fwd.fbx b/interface/resources/avatar/animations/walk_fwd.fbx index 7d8ab94494..8ad461f62e 100644 Binary files a/interface/resources/avatar/animations/walk_fwd.fbx and b/interface/resources/avatar/animations/walk_fwd.fbx differ diff --git a/interface/resources/avatar/animations/walk_fwd_fast.fbx b/interface/resources/avatar/animations/walk_fwd_fast.fbx index 1ba94a798f..db440fc9c4 100644 Binary files a/interface/resources/avatar/animations/walk_fwd_fast.fbx and b/interface/resources/avatar/animations/walk_fwd_fast.fbx differ diff --git a/interface/resources/avatar/animations/walk_short_bwd.fbx b/interface/resources/avatar/animations/walk_short_bwd.fbx index 89e940c888..d7e4bedf53 100644 Binary files a/interface/resources/avatar/animations/walk_short_bwd.fbx and b/interface/resources/avatar/animations/walk_short_bwd.fbx differ diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 6ebbbf3000..50fe5019f9 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -585,149 +585,188 @@ "states": [ { "id": "idle", - "interpTarget": 0, - "interpDuration": 4, + "interpTarget": 20, + "interpDuration": 8, "interpType": "snapshotPrev", "transitions": [ - { "var": "isMovingForward", "state": "idleToWalkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] }, { "id": "idleToWalkFwd", - "interpTarget": 10, - "interpDuration": 4, - "interpType": "snapshotPrev", + "interpTarget": 12, + "interpDuration": 8, "transitions": [ - { "var": "idleToWalkFwdOnDone", "state": "walkFwd" }, + { "var": "idleToWalkFwdOnDone", "state": "WALKFWD" }, { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] }, { "id": "idleSettle", - "interpTarget": 10, - "interpDuration": 10, + "interpTarget": 15, + "interpDuration": 8, "interpType": "snapshotPrev", "transitions": [ {"var": "idleSettleOnDone", "state": "idle" }, - {"var": "isMovingForward", "state": "idleToWalkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + {"var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" } + { "var": "isInAirRun", "state": "INAIRRUN" } ] }, { - "id": "walkFwd", - "interpTarget": 16, - "interpDuration": 6, + "id": "WALKFWD", + "interpTarget": 35, + "interpDuration": 10, "interpType": "snapshotPrev", "transitions": [ { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] }, { - "id": "walkBwd", - "interpTarget": 8, - "interpDuration": 6, + "id": "WALKBWD", + "interpTarget": 35, + "interpDuration": 10, "interpType": "snapshotPrev", "transitions": [ { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] }, { - "id": "strafeRight", - "interpTarget": 5, + "id": "STRAFERIGHT", + "interpTarget": 25, "interpDuration": 8, "interpType": "snapshotPrev", "transitions": [ { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] }, { - "id": "strafeLeft", - "interpTarget": 5, + "id": "STRAFELEFT", + "interpTarget": 25, "interpDuration": 8, "interpType": "snapshotPrev", "transitions": [ { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "turnRight", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { "var": "isNotTurning", "state": "idle" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningLeft", "state": "turnLeft" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "turnLeft", + "interpTarget": 6, + "interpDuration": 8, + "transitions": [ + { "var": "isNotTurning", "state": "idle" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] @@ -739,18 +778,18 @@ "interpType": "snapshotPrev", "transitions": [ { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" } + { "var": "isInAirRun", "state": "INAIRRUN" } ] }, { @@ -760,60 +799,18 @@ "interpType": "snapshotPrev", "transitions": [ { "var": "isNotMoving", "state": "idleSettle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" } - ] - }, - { - "id": "turnRight", - "interpTarget": 6, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotTurning", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } - ] - }, - { - "id": "turnLeft", - "interpTarget": 6, - "interpDuration": 8, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotTurning", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, - { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, - { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, - { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + { "var": "isInAirRun", "state": "INAIRRUN" } ] }, { @@ -826,79 +823,79 @@ }, { "id": "takeoffStand", - "interpTarget": 0, - "interpDuration": 6, + "interpTarget": 2, + "interpDuration": 2, "transitions": [ { "var": "isNotTakeoff", "state": "inAirStand" } ] }, { - "id": "takeoffRun", - "interpTarget": 0, - "interpDuration": 6, + "id": "TAKEOFFRUN", + "interpTarget": 2, + "interpDuration": 2, "transitions": [ - { "var": "isNotTakeoff", "state": "inAirRun" } + { "var": "isNotTakeoff", "state": "INAIRRUN" } ] }, { "id": "inAirStand", - "interpTarget": 0, - "interpDuration": 6, + "interpTarget": 3, + "interpDuration": 3, "interpType": "snapshotPrev", "transitions": [ { "var": "isNotInAir", "state": "landStandImpact" } ] }, { - "id": "inAirRun", - "interpTarget": 0, - "interpDuration": 6, + "id": "INAIRRUN", + "interpTarget": 3, + "interpDuration": 3, "interpType": "snapshotPrev", "transitions": [ - { "var": "isNotInAir", "state": "landRun" } + { "var": "isNotInAir", "state": "WALKFWD" } ] }, { "id": "landStandImpact", - "interpTarget": 6, - "interpDuration": 4, + "interpTarget": 1, + "interpDuration": 1, "transitions": [ { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "landStandImpactOnDone", "state": "landStand" } ] }, { "id": "landStand", - "interpTarget": 0, + "interpTarget": 1, "interpDuration": 1, "transitions": [ - { "var": "isMovingForward", "state": "idleToWalkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isMovingForward", "state": "WALKFWD" }, + { "var": "isMovingBackward", "state": "WALKBWD" }, + { "var": "isMovingRight", "state": "STRAFERIGHT" }, + { "var": "isMovingLeft", "state": "STRAFELEFT" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, { "var": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isInAirRun", "state": "INAIRRUN" }, { "var": "landStandOnDone", "state": "idle" }, { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } ] }, { - "id": "landRun", - "interpTarget": 1, - "interpDuration": 7, + "id": "LANDRUN", + "interpTarget": 2, + "interpDuration": 2, "transitions": [ { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, - { "var": "landRunOnDone", "state": "walkFwd" } + { "var": "isTakeoffRun", "state": "TAKEOFFRUN" }, + { "var": "landRunOnDone", "state": "WALKFWD" } ] } ] @@ -913,7 +910,7 @@ { "id": "idleStand", "interpTarget": 6, - "interpDuration": 6, + "interpDuration": 10, "transitions": [ { "var": "isTalking", "state": "idleTalk" } ] @@ -921,7 +918,7 @@ { "id": "idleTalk", "interpTarget": 6, - "interpDuration": 6, + "interpDuration": 10, "transitions": [ { "var": "notIsTalking", "state": "idleStand" } ] @@ -956,12 +953,12 @@ ] }, { - "id": "walkFwd", + "id": "WALKFWD", "type": "blendLinearMove", "data": { "alpha": 0.0, "desiredSpeed": 1.4, - "characteristicSpeeds": [0.5, 1.5, 2.5, 3.2, 4.5], + "characteristicSpeeds": [0.5, 1.8, 2.3, 3.2, 4.5], "alphaVar": "moveForwardAlpha", "desiredSpeedVar": "moveForwardSpeed" }, @@ -984,7 +981,7 @@ "data": { "url": "qrc:///avatar/animations/walk_fwd.fbx", "startFrame": 0.0, - "endFrame": 35.0, + "endFrame": 30.0, "timeScale": 1.0, "loopFlag": true }, @@ -1046,25 +1043,25 @@ "data": { "url": "qrc:///avatar/animations/settle_to_idle.fbx", "startFrame": 1.0, - "endFrame": 48.0, + "endFrame": 59.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "walkBwd", + "id": "WALKBWD", "type": "blendLinearMove", "data": { "alpha": 0.0, "desiredSpeed": 1.4, - "characteristicSpeeds": [0.6, 1.7], + "characteristicSpeeds": [0.6, 1.6, 2.3, 3.1], "alphaVar": "moveBackwardAlpha", "desiredSpeedVar": "moveBackwardSpeed" }, "children": [ { - "id": "walkBwdShort", + "id": "walkBwdShort_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_short_bwd.fbx", @@ -1076,7 +1073,7 @@ "children": [] }, { - "id": "walkBwdNormal", + "id": "walkBwdFast_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", @@ -1086,6 +1083,30 @@ "loopFlag": true }, "children": [] + }, + { + "id": "jogBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_bwd.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "runBwd_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_bwd.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] } ] }, @@ -1115,18 +1136,18 @@ "children": [] }, { - "id": "strafeLeft", + "id": "STRAFELEFT", "type": "blendLinearMove", "data": { "alpha": 0.0, "desiredSpeed": 1.4, - "characteristicSpeeds": [0, 0.5, 1.5, 2.6, 3.0], + "characteristicSpeeds": [0.1, 0.5, 1.0, 2.6, 3.0], "alphaVar": "moveLateralAlpha", "desiredSpeedVar": "moveLateralSpeed" }, "children": [ { - "id": "strafeLeftShort_c", + "id": "strafeLeftShortStep_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_short_left.fbx", @@ -1138,7 +1159,7 @@ "children": [] }, { - "id": "strafeLeft_c", + "id": "strafeLeftStep_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_left.fbx", @@ -1150,19 +1171,19 @@ "children": [] }, { - "id": "strafeLeftAnim_c", + "id": "strafeLeftWalk_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_left.fbx", "startFrame": 0.0, - "endFrame": 33.0, + "endFrame": 35.0, "timeScale": 1.0, "loopFlag": true }, "children": [] }, { - "id": "strafeLeftFast_c", + "id": "strafeLeftWalkFast_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_left_fast.fbx", @@ -1188,17 +1209,17 @@ ] }, { - "id": "strafeRight", + "id": "STRAFERIGHT", "type": "blendLinearMove", "data": { "alpha": 0.0, "desiredSpeed": 1.4, - "characteristicSpeeds": [0, 0.5, 1.5, 2.6, 3.0], + "characteristicSpeeds": [0.1, 0.5, 1.0, 2.6, 3.0], "alphaVar": "moveLateralAlpha", "desiredSpeedVar": "moveLateralSpeed" }, "children": [ { - "id": "stepRightShort_c", + "id": "strafeRightShortStep_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_short_left.fbx", @@ -1211,7 +1232,7 @@ "children": [] }, { - "id": "stepRight_c", + "id": "strafeRightStep_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_left.fbx", @@ -1224,12 +1245,12 @@ "children": [] }, { - "id": "strafeRight_c", + "id": "strafeRightWalk_c", "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_left.fbx", "startFrame": 0.0, - "endFrame": 33.0, + "endFrame": 35.0, "timeScale": 1.0, "loopFlag": true, "mirrorFlag": true @@ -1381,22 +1402,22 @@ "id": "takeoffStand", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_takeoff.fbx", - "startFrame": 17.0, - "endFrame": 25.0, + "url": "qrc:///avatar/animations/jump_standing_launch.fbx", + "startFrame": 2.0, + "endFrame": 16.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "takeoffRun", + "id": "TAKEOFFRUN", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_takeoff.fbx", - "startFrame": 1.0, - "endFrame": 2.5, - "timeScale": 0.01, + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 4.0, + "endFrame": 15.0, + "timeScale": 1.0, "loopFlag": false }, "children": [] @@ -1416,7 +1437,7 @@ "url": "qrc:///avatar/animations/jump_standing_apex.fbx", "startFrame": 0.0, "endFrame": 0.0, - "timeScale": 0.0, + "timeScale": 1.0, "loopFlag": false }, "children": [] @@ -1448,7 +1469,7 @@ ] }, { - "id": "inAirRun", + "id": "INAIRRUN", "type": "blendLinear", "data": { "alpha": 0.0, @@ -1459,10 +1480,10 @@ "id": "inAirRunPreApex", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_in_air.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 0.0, + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 16.0, + "endFrame": 16.0, + "timeScale": 1.0, "loopFlag": false }, "children": [] @@ -1471,9 +1492,9 @@ "id": "inAirRunApex", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_in_air.fbx", - "startFrame": 6.0, - "endFrame": 6.0, + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 22.0, + "endFrame": 22.0, "timeScale": 1.0, "loopFlag": false }, @@ -1483,9 +1504,9 @@ "id": "inAirRunPostApex", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_in_air.fbx", - "startFrame": 11.0, - "endFrame": 11.0, + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 33.0, + "endFrame": 33.0, "timeScale": 1.0, "loopFlag": false }, @@ -1497,7 +1518,7 @@ "id": "landStandImpact", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_land.fbx", + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", "startFrame": 1.0, "endFrame": 6.0, "timeScale": 1.0, @@ -1509,22 +1530,22 @@ "id": "landStand", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_land.fbx", + "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", "startFrame": 6.0, - "endFrame": 28.0, + "endFrame": 68.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "landRun", + "id": "LANDRUN", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_land.fbx", - "startFrame": 1.0, - "endFrame": 6.0, - "timeScale": 0.65, + "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "startFrame": 29.0, + "endFrame": 40.0, + "timeScale": 1.0, "loopFlag": false }, "children": [] diff --git a/interface/resources/icons/tablet-icons/people-a-msg.svg b/interface/resources/icons/tablet-icons/people-a-msg.svg new file mode 100644 index 0000000000..862ce936ce --- /dev/null +++ b/interface/resources/icons/tablet-icons/people-a-msg.svg @@ -0,0 +1,83 @@ + + + +image/svg+xml + + \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/people-i-msg.svg b/interface/resources/icons/tablet-icons/people-i-msg.svg new file mode 100644 index 0000000000..635a01be4b --- /dev/null +++ b/interface/resources/icons/tablet-icons/people-i-msg.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/interface/resources/images/eyeClosed.svg b/interface/resources/images/eyeClosed.svg new file mode 100644 index 0000000000..6719471b3d --- /dev/null +++ b/interface/resources/images/eyeClosed.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/interface/resources/images/eyeOpen.svg b/interface/resources/images/eyeOpen.svg new file mode 100644 index 0000000000..ec5ceb5238 --- /dev/null +++ b/interface/resources/images/eyeOpen.svg @@ -0,0 +1,4 @@ + + + + diff --git a/interface/resources/images/interstitialPage/goTo_button.png b/interface/resources/images/interstitialPage/goTo_button.png new file mode 100644 index 0000000000..7c1b0d8500 Binary files /dev/null and b/interface/resources/images/interstitialPage/goTo_button.png differ diff --git a/interface/resources/images/loadingBar_placard.png b/interface/resources/images/loadingBar_placard.png new file mode 100644 index 0000000000..9fd4c9e71f Binary files /dev/null and b/interface/resources/images/loadingBar_placard.png differ diff --git a/interface/resources/images/loadingBar_progress.png b/interface/resources/images/loadingBar_progress.png new file mode 100644 index 0000000000..2b1fb089d9 Binary files /dev/null and b/interface/resources/images/loadingBar_progress.png differ diff --git a/interface/resources/meshes/redirect/oopsDialog_auth.fbx b/interface/resources/meshes/redirect/oopsDialog_auth.fbx new file mode 100644 index 0000000000..3360de993f Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_auth.fbx differ diff --git a/interface/resources/meshes/redirect/oopsDialog_auth.png b/interface/resources/meshes/redirect/oopsDialog_auth.png new file mode 100644 index 0000000000..2165cfbfa9 Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_auth.png differ diff --git a/interface/resources/meshes/redirect/oopsDialog_protocol.fbx b/interface/resources/meshes/redirect/oopsDialog_protocol.fbx new file mode 100644 index 0000000000..794a5f2dfd Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_protocol.fbx differ diff --git a/interface/resources/meshes/redirect/oopsDialog_protocol.png b/interface/resources/meshes/redirect/oopsDialog_protocol.png new file mode 100644 index 0000000000..63c6e00c4e Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_protocol.png differ diff --git a/interface/resources/meshes/redirect/oopsDialog_timeout.fbx b/interface/resources/meshes/redirect/oopsDialog_timeout.fbx new file mode 100644 index 0000000000..846d38bbee Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_timeout.fbx differ diff --git a/interface/resources/meshes/redirect/oopsDialog_timeout.png b/interface/resources/meshes/redirect/oopsDialog_timeout.png new file mode 100644 index 0000000000..b118f4417c Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_timeout.png differ diff --git a/interface/resources/meshes/redirect/oopsDialog_vague.fbx b/interface/resources/meshes/redirect/oopsDialog_vague.fbx new file mode 100644 index 0000000000..707f09e51a Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_vague.fbx differ diff --git a/interface/resources/meshes/redirect/oopsDialog_vague.png b/interface/resources/meshes/redirect/oopsDialog_vague.png new file mode 100644 index 0000000000..8a02ad2c27 Binary files /dev/null and b/interface/resources/meshes/redirect/oopsDialog_vague.png differ diff --git a/interface/resources/qml/+android/Stats.qml b/interface/resources/qml/+android/Stats.qml index fe827f6ece..0dcb07e730 100644 --- a/interface/resources/qml/+android/Stats.qml +++ b/interface/resources/qml/+android/Stats.qml @@ -264,20 +264,27 @@ Item { StatText { text: "GPU: " + root.gpuFrameTime.toFixed(1) + " ms" } + StatText { + text: "Drawcalls: " + root.drawcalls + } StatText { text: "Triangles: " + root.triangles + " / Material Switches: " + root.materialSwitches } StatText { + visible: root.expanded; text: "GPU Free Memory: " + root.gpuFreeMemory + " MB"; } StatText { + visible: root.expanded; text: "GPU Textures: "; } StatText { + visible: root.expanded; text: " Count: " + root.gpuTextures; } StatText { + visible: root.expanded; text: " Pressure State: " + root.gpuTextureMemoryPressureState; } StatText { @@ -287,27 +294,35 @@ Item { text: " " + root.gpuTextureResourceMemory + " / " + root.gpuTextureResourcePopulatedMemory + " / " + root.texturePendingTransfers + " MB"; } StatText { + visible: root.expanded; text: " Resident Memory: " + root.gpuTextureResidentMemory + " MB"; } StatText { + visible: root.expanded; text: " Framebuffer Memory: " + root.gpuTextureFramebufferMemory + " MB"; } StatText { + visible: root.expanded; text: " External Memory: " + root.gpuTextureExternalMemory + " MB"; } StatText { + visible: root.expanded; text: "GPU Buffers: " } StatText { + visible: root.expanded; text: " Count: " + root.gpuBuffers; } StatText { + visible: root.expanded; text: " Memory: " + root.gpuBufferMemory + " MB"; } StatText { + visible: root.expanded; text: "GL Swapchain Memory: " + root.glContextSwapchainMemory + " MB"; } StatText { + visible: root.expanded; text: "QML Texture Memory: " + root.qmlTextureMemory + " MB"; } StatText { diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml new file mode 100644 index 0000000000..b1900cf0a7 --- /dev/null +++ b/interface/resources/qml/AnimStats.qml @@ -0,0 +1,165 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.3 +import '.' + +Item { + id: animStats + + anchors.leftMargin: 300 + objectName: "StatsItem" + property int modality: Qt.NonModal + implicitHeight: row.height + implicitWidth: row.width + + Component.onCompleted: { + animStats.parentChanged.connect(fill); + fill(); + } + Component.onDestruction: { + animStats.parentChanged.disconnect(fill); + } + + function fill() { + // This will cause a warning at shutdown, need to find another way to remove + // the warning other than filling the anchors to the parent + anchors.horizontalCenter = parent.horizontalCenter + } + + Hifi.AnimStats { + id: root + objectName: "AnimStats" + implicitHeight: row.height + implicitWidth: row.width + + anchors.horizontalCenter: parent.horizontalCenter + readonly property string bgColor: "#AA111111" + + Row { + id: row + spacing: 8 + + Rectangle { + width: firstCol.width + 8; + height: firstCol.height + 8; + color: root.bgColor; + + Column { + id: firstCol + spacing: 4; x: 4; y: 4; + + StatText { + text: root.positionText + } + StatText { + text: "Anim Vars:--------------------------------------------------------------------------------" + } + ListView { + width: secondCol.width + height: root.animVars.length * 15 + visible: root.animVars.length > 0; + model: root.animVars + delegate: StatText { + text: { + var actualText = modelData.split("|")[1]; + if (actualText) { + return actualText; + } else { + return modelData; + } + } + color: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(1.0, 1.0, 1.0, grayScale); + } + styleColor: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(0.0, 0.0, 0.0, grayScale); + } + } + } + } + } + + Rectangle { + width: secondCol.width + 8 + height: secondCol.height + 8 + color: root.bgColor; + + Column { + id: secondCol + spacing: 4; x: 4; y: 4; + + StatText { + text: root.rotationText + } + StatText { + text: "State Machines:---------------------------------------------------------------------------" + } + ListView { + width: firstCol.width + height: root.animStateMachines.length * 15 + visible: root.animStateMachines.length > 0; + model: root.animStateMachines + delegate: StatText { + text: { + return modelData; + } + } + } + + } + } + + Rectangle { + width: thirdCol.width + 8 + height: thirdCol.height + 8 + color: root.bgColor; + + Column { + id: thirdCol + spacing: 4; x: 4; y: 4; + + StatText { + text: root.velocityText + } + StatText { + text: "Alpha Values:--------------------------------------------------------------------------" + } + ListView { + width: thirdCol.width + height: root.animAlphaValues.length * 15 + visible: root.animAlphaValues.length > 0; + model: root.animAlphaValues + delegate: StatText { + text: { + var actualText = modelData.split("|")[1]; + if (actualText) { + return actualText; + } else { + return modelData; + } + } + color: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(1.0, 1.0, 1.0, grayScale); + } + styleColor: { + var grayScale = parseFloat(modelData.split("|")[0]); + return Qt.rgba(0.0, 0.0, 0.0, grayScale); + } + } + } + + } + } + } + + Connections { + target: root.parent + onWidthChanged: { + root.x = root.parent.width - root.width; + } + } + } + +} diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml index 800026710d..e8ddbf823d 100644 --- a/interface/resources/qml/InteractiveWindow.qml +++ b/interface/resources/qml/InteractiveWindow.qml @@ -146,7 +146,8 @@ Windows.Window { Qt.WindowCloseButtonHint | Qt.WindowMaximizeButtonHint | Qt.WindowMinimizeButtonHint; - if ((flags & Desktop.ALWAYS_ON_TOP) === Desktop.ALWAYS_ON_TOP) { + // only use the always on top feature for non Windows OS + if (Qt.platform.os !== "windows" && (flags & Desktop.ALWAYS_ON_TOP)) { nativeWindowFlags |= Qt.WindowStaysOnTopHint; } nativeWindow.flags = nativeWindowFlags; diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index e21e8b7354..336858502d 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -23,6 +23,7 @@ ModalWindow { objectName: "LoginDialog" implicitWidth: 520 implicitHeight: 320 + closeButtonVisible: true destroyOnCloseButton: true destroyOnHidden: true visible: true diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml index bf7807c85d..96b638c911 100644 --- a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml @@ -117,27 +117,27 @@ Item { } spacing: hifi.dimensions.contentSpacing.y / 2 - TextField { - id: usernameField - anchors { - horizontalCenter: parent.horizontalCenter - } - width: 1080 - placeholderText: qsTr("Username or Email") + TextField { + id: usernameField + anchors { + horizontalCenter: parent.horizontalCenter } + width: 1080 + placeholderText: qsTr("Username or Email") + } - TextField { - id: passwordField - anchors { - horizontalCenter: parent.horizontalCenter - } - width: 1080 - - placeholderText: qsTr("Password") - echoMode: TextInput.Password - - Keys.onReturnPressed: linkAccountBody.login() + TextField { + id: passwordField + anchors { + horizontalCenter: parent.horizontalCenter } + width: 1080 + + placeholderText: qsTr("Password") + echoMode: TextInput.Password + + Keys.onReturnPressed: linkAccountBody.login() + } } InfoItem { @@ -176,7 +176,7 @@ Item { anchors { left: parent.left top: form.bottom - topMargin: hifi.dimensions.contentSpacing.y / 2 + topMargin: hifi.dimensions.contentSpacing.y / 2 } spacing: hifi.dimensions.contentSpacing.x @@ -201,7 +201,7 @@ Item { anchors { right: parent.right top: form.bottom - topMargin: hifi.dimensions.contentSpacing.y / 2 + topMargin: hifi.dimensions.contentSpacing.y / 2 } spacing: hifi.dimensions.contentSpacing.x onHeightChanged: d.resize(); onWidthChanged: d.resize(); diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 4c6e5f6fce..57293cb5e3 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -15,7 +15,6 @@ import QtQuick.Controls.Styles 1.4 as OriginalStyles import "../controls-uit" import "../styles-uit" - Item { id: linkAccountBody clip: true @@ -87,6 +86,23 @@ Item { height: 48 } + ShortcutText { + id: flavorText + anchors { + top: parent.top + left: parent.left + margins: 0 + topMargin: hifi.dimensions.contentSpacing.y + } + + text: qsTr("Sign in to High Fidelity to make friends, get HFC, and buy interesting things on the Marketplace!") + width: parent.width + wrapMode: Text.WordWrap + lineHeight: 1 + lineHeightMode: Text.ProportionalHeight + horizontalAlignment: Text.AlignHCenter + } + ShortcutText { id: mainTextContainer anchors { @@ -97,7 +113,6 @@ Item { } visible: false - text: qsTr("Username or password incorrect.") wrapMode: Text.WordWrap color: hifi.colors.redAccent @@ -117,22 +132,21 @@ Item { } spacing: 2 * hifi.dimensions.contentSpacing.y - TextField { id: usernameField text: Settings.getValue("wallet/savedUsername", ""); width: parent.width focus: true - label: "Username or Email" + placeholderText: "Username or Email" activeFocusOnPress: true ShortcutText { z: 10 + y: usernameField.height anchors { - left: usernameField.left - top: usernameField.top - leftMargin: usernameField.textFieldLabel.contentWidth + 10 - topMargin: -19 + right: usernameField.right + top: usernameField.bottom + topMargin: 4 } text: "Forgot Username?" @@ -143,26 +157,32 @@ Item { onLinkActivated: loginDialog.openUrl(link) } + onFocusChanged: { root.text = ""; } + Component.onCompleted: { + var savedUsername = Settings.getValue("wallet/savedUsername", ""); + usernameField.text = savedUsername === "Unknown user" ? "" : savedUsername; + } } TextField { id: passwordField width: parent.width - - label: "Password" - echoMode: showPassword.checked ? TextInput.Normal : TextInput.Password + placeholderText: "Password" activeFocusOnPress: true + echoMode: TextInput.Password + onHeightChanged: d.resize(); onWidthChanged: d.resize(); ShortcutText { + id: forgotPasswordShortcut + y: passwordField.height z: 10 anchors { - left: passwordField.left - top: passwordField.top - leftMargin: passwordField.textFieldLabel.contentWidth + 10 - topMargin: -19 + right: passwordField.right + top: passwordField.bottom + topMargin: 4 } text: "Forgot Password?" @@ -179,12 +199,45 @@ Item { root.isPassword = true; } - Keys.onReturnPressed: linkAccountBody.login() - } + Rectangle { + id: showPasswordHitbox + z: 10 + x: passwordField.width - ((passwordField.height) * 31 / 23) + width: parent.width - (parent.width - (parent.height * 31/16)) + height: parent.height + anchors { + right: parent.right + } + color: "transparent" - CheckBox { - id: showPassword - text: "Show password" + Image { + id: showPasswordImage + y: (passwordField.height - (passwordField.height * 16 / 23)) / 2 + width: passwordField.width - (passwordField.width - (((passwordField.height) * 31/23))) + height: passwordField.height * 16 / 23 + anchors { + right: parent.right + rightMargin: 3 + } + source: "../../images/eyeOpen.svg" + } + + MouseArea { + id: passwordFieldMouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton + property bool showPassword: false + onClicked: { + showPassword = !showPassword; + passwordField.echoMode = showPassword ? TextInput.Normal : TextInput.Password; + showPasswordImage.source = showPassword ? "../../images/eyeClosed.svg" : "../../images/eyeOpen.svg"; + showPasswordImage.height = showPassword ? passwordField.height : passwordField.height * 16 / 23; + showPasswordImage.y = showPassword ? 0 : (passwordField.height - showPasswordImage.height) / 2; + } + } + } + + Keys.onReturnPressed: linkAccountBody.login() } InfoItem { @@ -206,6 +259,26 @@ Item { onHeightChanged: d.resize(); onWidthChanged: d.resize(); anchors.horizontalCenter: parent.horizontalCenter + CheckBox { + id: autoLogoutCheckbox + checked: !Settings.getValue("wallet/autoLogout", true) + text: "Keep me signed in" + boxSize: 20; + labelFontSize: 15 + color: hifi.colors.black + onCheckedChanged: { + Settings.setValue("wallet/autoLogout", !checked); + if (checked) { + Settings.setValue("wallet/savedUsername", Account.username); + } else { + Settings.setValue("wallet/savedUsername", ""); + } + } + Component.onDestruction: { + Settings.setValue("wallet/autoLogout", !checked); + } + } + Button { id: linkAccountButton anchors.verticalCenter: parent.verticalCenter @@ -216,12 +289,6 @@ Item { onClicked: linkAccountBody.login() } - - Button { - anchors.verticalCenter: parent.verticalCenter - text: qsTr("Cancel") - onClicked: root.tryDestroy() - } } Row { @@ -234,7 +301,7 @@ Item { RalewaySemiBold { size: hifi.fontSizes.inputLabel anchors.verticalCenter: parent.verticalCenter - text: qsTr("Don't have an account?") + text: qsTr("New to High Fidelity?") } Button { @@ -279,7 +346,15 @@ Item { target: loginDialog onHandleLoginCompleted: { console.log("Login Succeeded, linking steam account") - + var poppedUp = Settings.getValue("loginDialogPoppedUp", false); + if (poppedUp) { + console.log("[ENCOURAGELOGINDIALOG]: logging in") + var data = { + "action": "user logged in" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + Settings.setValue("loginDialogPoppedUp", false); + } if (loginDialog.isSteamRunning()) { loginDialog.linkSteam() } else { @@ -290,6 +365,15 @@ Item { } onHandleLoginFailed: { console.log("Login Failed") + var poppedUp = Settings.getValue("loginDialogPoppedUp", false); + if (poppedUp) { + console.log("[ENCOURAGELOGINDIALOG]: failed logging in") + var data = { + "action": "user failed logging in" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + Settings.setValue("loginDialogPoppedUp", false); + } mainTextContainer.visible = true toggleLoading(false) } diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 54798f8762..1a29ce87df 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -119,6 +119,22 @@ Item { visible: root.expanded text: "Avatars NOT Updated: " + root.notUpdatedAvatarCount } + StatText { + visible: root.expanded + text: "Total picks:\n " + + root.stylusPicksCount + " styluses\n " + + root.rayPicksCount + " rays\n " + + root.parabolaPicksCount + " parabolas\n " + + root.collisionPicksCount + " colliders" + } + StatText { + visible: root.expanded + text: "Intersection calls: Entities/Overlays/Avatars/HUD\n " + + "Styluses:\t" + root.stylusPicksUpdated.x + "/" + root.stylusPicksUpdated.y + "/" + root.stylusPicksUpdated.z + "/" + root.stylusPicksUpdated.w + "\n " + + "Rays:\t" + root.rayPicksUpdated.x + "/" + root.rayPicksUpdated.y + "/" + root.rayPicksUpdated.z + "/" + root.rayPicksUpdated.w + "\n " + + "Parabolas:\t" + root.parabolaPicksUpdated.x + "/" + root.parabolaPicksUpdated.y + "/" + root.parabolaPicksUpdated.z + "/" + root.parabolaPicksUpdated.w + "\n " + + "Colliders:\t" + root.collisionPicksUpdated.x + "/" + root.collisionPicksUpdated.y + "/" + root.collisionPicksUpdated.z + "/" + root.collisionPicksUpdated.w + } } } @@ -176,21 +192,6 @@ Item { StatText { text: "Yaw: " + root.yaw.toFixed(1) } - StatText { - visible: root.animStackNames.length > 0; - text: "Anim Stack Names:" - } - ListView { - width: geoCol.width - height: root.animStackNames.length * 15 - visible: root.animStackNames.length > 0; - model: root.animStackNames - delegate: StatText { - text: modelData.length > 30 - ? modelData.substring(0, 5) + "..." + modelData.substring(modelData.length - 22) - : modelData - } - } StatText { visible: root.expanded; text: "Avatar Mixer In: " + root.avatarMixerInKbps + " kbps, " + @@ -288,52 +289,69 @@ Item { StatText { text: "GPU frame size: " + root.gpuFrameSize.x + " x " + root.gpuFrameSize.y } + StatText { + text: "Drawcalls: " + root.drawcalls + } StatText { text: "Triangles: " + root.triangles + " / Material Switches: " + root.materialSwitches } StatText { + visible: root.expanded; text: "GPU Free Memory: " + root.gpuFreeMemory + " MB"; } StatText { + visible: root.expanded; text: "GPU Textures: "; } StatText { + visible: root.expanded; text: " Count: " + root.gpuTextures; } StatText { + visible: root.expanded; text: " Pressure State: " + root.gpuTextureMemoryPressureState; } StatText { + visible: root.expanded; property bool showIdeal: (root.gpuTextureResourceIdealMemory != root.gpuTextureResourceMemory); text: " Resource Allocated " + (showIdeal ? "(Ideal)" : "") + " / Populated / Pending: "; } StatText { + visible: root.expanded; property bool showIdeal: (root.gpuTextureResourceIdealMemory != root.gpuTextureResourceMemory); text: " " + root.gpuTextureResourceMemory + (showIdeal ? ("(" + root.gpuTextureResourceIdealMemory + ")") : "") + " / " + root.gpuTextureResourcePopulatedMemory + " / " + root.texturePendingTransfers + " MB"; } StatText { + visible: root.expanded; text: " Resident Memory: " + root.gpuTextureResidentMemory + " MB"; } StatText { + visible: root.expanded; text: " Framebuffer Memory: " + root.gpuTextureFramebufferMemory + " MB"; } StatText { + visible: root.expanded; text: " External Memory: " + root.gpuTextureExternalMemory + " MB"; } StatText { + visible: root.expanded; text: "GPU Buffers: " } StatText { + visible: root.expanded; text: " Count: " + root.gpuBuffers; } StatText { + visible: root.expanded; text: " Memory: " + root.gpuBufferMemory + " MB"; } StatText { + visible: root.expanded; text: "GL Swapchain Memory: " + root.glContextSwapchainMemory + " MB"; } StatText { + visible: root.expanded; text: "QML Texture Memory: " + root.qmlTextureMemory + " MB"; } StatText { diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index bc958aa876..db695dbfb2 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -52,12 +52,18 @@ Item { id: back enabledColor: hifi.colors.darkGray disabledColor: hifi.colors.lightGrayText - enabled: historyIndex > 0 + enabled: true text: "BACK" MouseArea { anchors.fill: parent - onClicked: goBack() + onClicked: { + if (historyIndex > 0) { + goBack(); + } else { + closeWebEngine(); + } + } } } diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index d8eae311d8..aea5931627 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -46,7 +46,7 @@ Rectangle { } } - property var jointNames; + property var jointNames: [] property var currentAvatarSettings; function fetchAvatarModelName(marketId, avatar) { @@ -175,7 +175,14 @@ Rectangle { displayNameInput.text = getAvatarsData.displayName; currentAvatarSettings = getAvatarsData.currentAvatarSettings; - updateCurrentAvatarInBookmarks(currentAvatar); + var bookmarkAvatarIndex = allAvatars.findAvatarIndexByValue(currentAvatar); + if (bookmarkAvatarIndex === -1) { + currentAvatar.name = ''; + } else { + currentAvatar.name = allAvatars.get(bookmarkAvatarIndex).name; + allAvatars.move(bookmarkAvatarIndex, 0, 1); + } + view.setPage(0); } else if (message.method === 'updateAvatarInBookmarks') { updateCurrentAvatarInBookmarks(currentAvatar); } else if (message.method === 'selectAvatarEntity') { diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index abcceae295..dfa6555150 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -530,9 +530,7 @@ Item { maximumValue: 20.0 stepSize: 5 updateValueWhileDragging: true - Component.onCompleted: { - value = Users.getAvatarGain(uuid); - } + value: Users.getAvatarGain(uuid) onValueChanged: { updateGainFromQML(uuid, value, false); } diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index cbab83ea28..e8fc41da63 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -271,6 +271,8 @@ Rectangle { connectionsUserModel.getFirstPage(); } activeTab = "connectionsTab"; + connectionsOnlineDot.visible = false; + pal.sendToScript({method: 'hideNotificationDot'}); connectionsHelpText.color = hifi.colors.blueAccent; } } @@ -298,6 +300,16 @@ Rectangle { } } } + Rectangle { + id: connectionsOnlineDot; + visible: false; + width: 10; + height: width; + radius: width; + color: "#EF3B4E" + anchors.left: parent.left; + anchors.verticalCenter: parent.verticalCenter; + } // "CONNECTIONS" text RalewaySemiBold { id: connectionsTabSelectorText; @@ -305,7 +317,11 @@ Rectangle { // Text size size: hifi.fontSizes.tabularData; // Anchors - anchors.fill: parent; + anchors.left: connectionsOnlineDot.visible ? connectionsOnlineDot.right : parent.left; + anchors.leftMargin: connectionsOnlineDot.visible ? 4 : 0; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.right: parent.right; // Style font.capitalization: Font.AllUppercase; color: activeTab === "connectionsTab" ? hifi.colors.blueAccent : hifi.colors.baseGray; @@ -326,7 +342,7 @@ Rectangle { anchors.left: connectionsTabSelectorTextContainer.left; anchors.top: connectionsTabSelectorTextContainer.top; anchors.topMargin: 1; - anchors.leftMargin: connectionsTabSelectorTextMetrics.width + 42; + anchors.leftMargin: connectionsTabSelectorTextMetrics.width + 42 + connectionsOnlineDot.width + connectionsTabSelectorText.anchors.leftMargin; RalewayRegular { id: connectionsHelpText; text: "[?]"; @@ -780,6 +796,12 @@ Rectangle { headerVisible: true; sortIndicatorColumn: settings.connectionsSortIndicatorColumn; sortIndicatorOrder: settings.connectionsSortIndicatorOrder; + onSortIndicatorColumnChanged: { + settings.connectionsSortIndicatorColumn = sortIndicatorColumn; + } + onSortIndicatorOrderChanged: { + settings.connectionsSortIndicatorOrder = sortIndicatorOrder; + } TableViewColumn { id: connectionsUserNameHeader; @@ -1261,6 +1283,9 @@ Rectangle { case 'http.response': http.handleHttpResponse(message); break; + case 'changeConnectionsDotStatus': + connectionsOnlineDot.visible = message.shouldShowDot; + break; default: console.log('Unrecognized message:', JSON.stringify(message)); } diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index cc1ba49984..f4a708567a 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -26,7 +26,7 @@ Rectangle { HifiConstants { id: hifi; } property var eventBridge; - property string title: "Audio Settings - " + AudioScriptingInterface.context; + property string title: "Audio Settings" signal sendToScript(var message); color: hifi.colors.baseGray; diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index e80dab60c2..5bf867a331 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -25,7 +25,17 @@ Rectangle { modified = false; } - property var jointNames; + property var jointNames: [] + onJointNamesChanged: { + jointsModel.clear(); + for (var i = 0; i < jointNames.length; ++i) { + var jointName = jointNames[i]; + if (jointName !== 'LeftHand' && jointName !== 'RightHand') { + jointsModel.append({'text' : jointName, 'jointIndex' : i}); + } + } + } + property string avatarName: '' property var wearablesModel; @@ -268,20 +278,30 @@ Rectangle { anchors.right: parent.right enabled: getCurrentWearable() !== null && !isSoft.checked comboBox.displayText: isSoft.checked ? 'Hips' : comboBox.currentText + comboBox.textRole: "text" - model: jointNames + model: ListModel { + id: jointsModel + } property bool notify: false function set(jointIndex) { notify = false; - currentIndex = jointIndex; + for (var i = 0; i < jointsModel.count; ++i) { + if (jointsModel.get(i).jointIndex === jointIndex) { + currentIndex = i; + break; + } + } notify = true; } function notifyJointChanged() { modified = true; + var jointIndex = jointsModel.get(jointsCombobox.currentIndex).jointIndex; + var properties = { - parentJointIndex: currentIndex, + parentJointIndex: jointIndex, localPosition: { x: positionVector.xvalue, y: positionVector.yvalue, @@ -294,7 +314,7 @@ Rectangle { } }; - wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties); + wearableUpdated(getCurrentWearable().id, jointIndex, properties); } onCurrentIndexChanged: { diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 65d98af234..47b9d354d0 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -408,9 +408,7 @@ Rectangle { Connections { onSendSignalToWallet: { - if (msg.method === 'walletReset' || msg.method === 'passphraseReset') { - sendToScript(msg); - } else if (msg.method === 'walletSecurity_changeSecurityImage') { + if (msg.method === 'walletSecurity_changeSecurityImage') { securityImageChange.initModel(); root.activeView = "securityImageChange"; } diff --git a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml index 1ec7ea3e0e..afe06897df 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml @@ -18,6 +18,7 @@ import "../../windows" Rectangle { id: root objectName: "DCConectionTiming" + property string title: "Domain Connection Timing" signal sendToScript(var message); property bool isHMD: false @@ -33,7 +34,7 @@ Rectangle { Row { id: header anchors.top: parent.top - anchors.topMargin: hifi.dimensions.tabletMenuHeader + anchors.topMargin: hifi.dimensions.contentMargin.y anchors.leftMargin: 5 anchors.rightMargin: 5 anchors.left: parent.left diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml index 7bce460283..24798af21a 100644 --- a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml @@ -18,6 +18,7 @@ import "../../windows" Rectangle { id: root objectName: "EntityStatistics" + property string title: "Entity Statistics" signal sendToScript(var message); property bool isHMD: false @@ -40,6 +41,7 @@ Rectangle { id: scrollView width: parent.width anchors.top: parent.top + anchors.topMargin: hifi.dimensions.contentMargin.y anchors.bottom: parent.bottom anchors.bottomMargin: hifi.dimensions.tabletMenuHeader contentWidth: column.implicitWidth @@ -48,10 +50,15 @@ Rectangle { Column { id: column - anchors.margins: 10 + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - y: hifi.dimensions.tabletMenuHeader //-bgNavBar + anchors { + topMargin: 0 + leftMargin: 10 + rightMargin: 10 + bottomMargin: 0 + } spacing: 20 TabletEntityStatisticsItem { diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index 135c1379e2..6706830537 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -24,6 +24,8 @@ Item { height: parent.height width: parent.width + property string title: "Controls" + HifiConstants { id: hifi } TabBar { diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index c9d05aea51..3d518289fb 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -79,7 +79,7 @@ StackView { return; } location.text = targetString; - toggleOrGo(true, targetString); + toggleOrGo(targetString, true); clearAddressLineTimer.start(); } @@ -105,7 +105,6 @@ StackView { propagateComposedEvents: true onPressed: { parent.forceActiveFocus(); - addressBarDialog.keyboardEnabled = false; mouse.accepted = false; } } @@ -223,7 +222,6 @@ StackView { updateLocationText(text.length > 0); } onAccepted: { - addressBarDialog.keyboardEnabled = false; toggleOrGo(); } @@ -378,7 +376,7 @@ StackView { HifiControls.Keyboard { id: keyboard - raised: parent.keyboardEnabled + raised: parent.keyboardEnabled && parent.keyboardRaised numeric: parent.punctuationMode anchors { bottom: parent.bottom @@ -401,7 +399,7 @@ StackView { } } - function toggleOrGo(fromSuggestions, address) { + function toggleOrGo(address, fromSuggestions) { if (address !== undefined && address !== "") { addressBarDialog.loadAddress(address, fromSuggestions); clearAddressLineTimer.start(); diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index 57c1163eb8..6540d53fca 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -23,6 +23,8 @@ FocusScope { property string subMenu: "" signal sendToScript(var message); + HifiConstants { id: hifi } + Rectangle { id: bgNavBar height: 90 @@ -45,24 +47,22 @@ FocusScope { anchors.topMargin: 0 anchors.top: parent.top - Image { + HiFiGlyphs { id: menuRootIcon - width: 40 - height: 40 - source: "../../../icons/tablet-icons/menu-i.svg" + text: breadcrumbText.text !== "Menu" ? hifi.glyphs.backward : "" + size: 72 anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: 15 + width: breadcrumbText.text === "Menu" ? 32 : 50 + visible: breadcrumbText.text !== "Menu" MouseArea { anchors.fill: parent hoverEnabled: true onEntered: iconColorOverlay.color = "#1fc6a6"; onExited: iconColorOverlay.color = "#34a2c7"; - // navigate back to root level menu onClicked: { - buildMenu(); - breadcrumbText.text = "Menu"; + menuPopperUpper.closeLastMenu(); tabletRoot.playButtonClickSound(); } } @@ -79,23 +79,10 @@ FocusScope { id: breadcrumbText text: "Menu" size: 26 - color: "#34a2c7" + color: "#e3e3e3" anchors.verticalCenter: parent.verticalCenter anchors.left: menuRootIcon.right anchors.leftMargin: 15 - MouseArea { - anchors.fill: parent - hoverEnabled: true - onEntered: breadcrumbText.color = "#1fc6a6"; - onExited: breadcrumbText.color = "#34a2c7"; - // navigate back to parent level menu if there is one - onClicked: { - if (breadcrumbText.text !== "Menu") { - menuPopperUpper.closeLastMenu(); - } - tabletRoot.playButtonClickSound(); - } - } } } @@ -103,7 +90,6 @@ FocusScope { menuPopperUpper.closeLastMenu(); } - function setRootMenu(rootMenu, subMenu) { tabletMenu.subMenu = subMenu; tabletMenu.rootMenu = rootMenu; diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index 4d36a57793..fe636dafa5 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -1,5 +1,5 @@ // -// MessageDialog.qml +// TabletMenuStack.qml // // Created by Dante Ruiz on 13 Feb 2017 // Copyright 2016 High Fidelity, Inc. @@ -66,7 +66,7 @@ Item { function popSource() { console.log("trying to pop page"); - d.pop(); + closeLastMenu(); } function toModel(items, newMenu) { diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml index 05f45dc61e..ec2c003383 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml @@ -41,7 +41,15 @@ Item { section.saveAll(); } - closeDialog(); + if (HMD.active) { + if (gotoPreviousApp) { + tablet.returnToPreviousApp(); + } else { + tablet.popFromStack(); + } + } else { + closeDialog(); + } } function restoreAll() { @@ -50,7 +58,15 @@ Item { section.restoreAll(); } - closeDialog(); + if (HMD.active) { + if (gotoPreviousApp) { + tablet.returnToPreviousApp(); + } else { + tablet.popFromStack(); + } + } else { + closeDialog(); + } } function closeDialog() { diff --git a/interface/resources/qml/windows/ModalFrame.qml b/interface/resources/qml/windows/ModalFrame.qml index 211353b5f3..cb23ccd5ad 100644 --- a/interface/resources/qml/windows/ModalFrame.qml +++ b/interface/resources/qml/windows/ModalFrame.qml @@ -94,5 +94,25 @@ Frame { color: hifi.colors.lightGray } } + + + GlyphButton { + id: closeButton + visible: window.closeButtonVisible + width: 30 + y: -hifi.dimensions.modalDialogTitleHeight + anchors { + top: parent.top + right: parent.right + topMargin: 10 + rightMargin: 10 + } + glyph: hifi.glyphs.close + size: 23 + onClicked: { + window.clickedCloseButton = true; + window.destroy(); + } + } } } diff --git a/interface/resources/qml/windows/ModalWindow.qml b/interface/resources/qml/windows/ModalWindow.qml index 2d56099051..ebd5293b0a 100644 --- a/interface/resources/qml/windows/ModalWindow.qml +++ b/interface/resources/qml/windows/ModalWindow.qml @@ -19,6 +19,9 @@ ScrollingWindow { destroyOnHidden: true frame: ModalFrame { } + property bool closeButtonVisible: false + // only applicable for if close button is visible. + property bool clickedCloseButton: false property int colorScheme: hifi.colorSchemes.light property bool draggable: false diff --git a/interface/resources/serverless/redirect.json b/interface/resources/serverless/redirect.json new file mode 100644 index 0000000000..64cb4d8a3f --- /dev/null +++ b/interface/resources/serverless/redirect.json @@ -0,0 +1,934 @@ +{ + "DataVersion": 0, + "Paths": + { + "/": "/4,1.4,4/0,0.49544,0,0.868645" + }, + "Entities": [ + { + "clientOnly": false, + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "created": "2018-09-05T18:13:00Z", + "dimensions": { + "blue": 1.159199833869934, + "green": 2.8062009811401367, + "red": 1.6216505765914917, + "x": 1.6216505765914917, + "y": 2.8062009811401367, + "z": 1.159199833869934 + }, + "id": "{d0ed60b8-9174-4c56-8e78-2c5399329ae0}", + "lastEdited": 1536171372916208, + "lastEditedBy": "{151cb20e-715a-4c80-aa0d-5b58b1c8a0c9}", + "locked": true, + "name": "Try Again Zone", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue":4.015342712402344, + "green":1.649999976158142, + "red":2.00921893119812, + "x":2.00921893119812, + "y":1.649999976158142, + "z":4.015342712402344 + }, + "queryAACube": { + "scale": 3.4421300888061523, + "x": 1.6001315116882324, + "y": -0.07100248336791992, + "z": 0.14220571517944336 + }, + "rotation": { + "w": 0.9914448857307434, + "x": 0, + "y": -0.13052619993686676, + "z": 0 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/zoneTryAgainEntityScript.js", + "shapeType": "box", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 255 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 8.645400047302246, + "green": 0.20000000298023224, + "red": 20.025121688842773, + "x": 20.025121688842773, + "y": 0.20000000298023224, + "z": 8.645400047302246 + }, + "id": "{e44fb546-b34a-4966-9b11-73556f800d21}", + "lastEdited": 1536107948776951, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "ceiling", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 4.846520900726318, + "green": 2.912982940673828, + "red": 5.739595890045166, + "x": 5.739595890045166, + "y": 2.912982940673828, + "z": 4.846520900726318 + }, + "queryAACube": { + "scale": 21.812576293945312, + "x": -5.16669225692749, + "y": -7.993305206298828, + "z": -6.059767246246338 + }, + "rotation": { + "w": 0.970295786857605, + "x": 0, + "y": -0.24192190170288086, + "z": 0 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 6.9401350021362305, + "green": 0.04553089290857315, + "red": 7.004304885864258, + "x": 7.004304885864258, + "y": 0.04553089290857315, + "z": 6.9401350021362305 + }, + "id": "{8cd93fe5-16c0-44b7-b1e9-e7e06c4e9228}", + "lastEdited": 1536107948774796, + "lastEditedBy": "{4eecd88f-ef9b-4a83-bb9a-7f7496209c6b}", + "locked": true, + "name": "floor", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.6175529956817627, + "green": 0, + "red": 4.102385997772217, + "x": 4.102385997772217, + "y": 0, + "z": 3.6175529956817627 + }, + "queryAACube": { + "scale": 9.860417366027832, + "x": -0.8278226852416992, + "y": -4.930208683013916, + "z": -1.3126556873321533 + }, + "rotation": { + "w": 0.8660253882408142, + "x": -1.5922749298624694e-05, + "y": 0.5, + "z": -4.572480611386709e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{147272dc-a344-4171-9621-efc1c2095997}", + "lastEdited": 1536107948776823, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "leftWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 6.1806135177612305, + "green": 1.0066027641296387, + "red": 1.4690406322479248, + "x": 1.4690406322479248, + "y": 1.0066027641296387, + "z": 6.1806135177612305 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -4.371703147888184, + "y": -4.834141254425049, + "z": 0.33986949920654297 + }, + "rotation": { + "w": 0.8637980222702026, + "x": -4.57763671875e-05, + "y": 0.5038070678710938, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{5f2b89b8-47e3-4915-a966-d46307a40f06}", + "lastEdited": 1536107948774605, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "backWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 5.268576622009277, + "green": 1.0066027641296387, + "red": 6.093774318695068, + "x": 6.093774318695068, + "y": 1.0066027641296387, + "z": 5.268576622009277 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": 0.25303030014038086, + "y": -4.834141254425049, + "z": -0.5721673965454102 + }, + "rotation": { + "w": 0.9662165641784668, + "x": -4.57763671875e-05, + "y": -0.2576791048049927, + "z": 1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 14.40000057220459, + "green": 14.40000057220459, + "red": 14.40000057220459, + "x": 14.40000057220459, + "y": 14.40000057220459, + "z": 14.40000057220459 + }, + "id": "{baf96345-8f68-4068-af4c-3c690035852a}", + "lastEdited": 1536107948775591, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 2.3440732955932617, + "green": 1.6162219047546387, + "red": 1.8748211860656738, + "x": 1.8748211860656738, + "y": 1.6162219047546387, + "z": 2.3440732955932617 + }, + "queryAACube": { + "scale": 24.9415340423584, + "x": -10.595945358276367, + "y": -10.854545593261719, + "z": -10.126693725585938 + }, + "rotation": { + "w": 0.8697794675827026, + "x": -1.52587890625e-05, + "y": 0.4933699369430542, + "z": -4.57763671875e-05 + }, + "shapeType": "box", + "skyboxMode": "enabled", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 1, + "clientOnly": false, + "color": { + "blue": 211, + "green": 227, + "red": 104 + }, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 211, + "green": 227, + "red": 104, + "x": 104, + "y": 227, + "z": 211 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.5, + "green": 2.5, + "red": 2.5, + "x": 2.5, + "y": 2.5, + "z": 2.5 + }, + "emitAcceleration": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 0.9993909597396851, + "x": 0.034897372126579285, + "y": -1.525880907138344e-05, + "z": -1.525880907138344e-05 + }, + "emitRate": 2, + "emitSpeed": 0, + "id": "{639a51f0-8613-4e46-bc7e-fef24597df73}", + "lastEdited": 1536107948776693, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 40, + "name": "Rays", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.75, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 1.3553659915924072, + "green": 1.2890124320983887, + "red": 2.5663273334503174, + "x": 2.5663273334503174, + "y": 1.2890124320983887, + "z": 1.3553659915924072 + }, + "queryAACube": { + "scale": 4.330127239227295, + "x": 0.4012637138366699, + "y": -0.8760511875152588, + "z": -0.8096976280212402 + }, + "radiusFinish": 0.10000000149011612, + "radiusStart": 0, + "rotation": { + "w": 0.9803768396377563, + "x": -1.52587890625e-05, + "y": 0.19707024097442627, + "z": -7.62939453125e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 1, + "clientOnly": false, + "color": { + "blue": 255, + "green": 205, + "red": 3 + }, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 204, + "red": 0, + "x": 0, + "y": 204, + "z": 255 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.5, + "green": 2.5, + "red": 2.5, + "x": 2.5, + "y": 2.5, + "z": 2.5 + }, + "emitAcceleration": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 0.9993909597396851, + "x": 0.034897372126579285, + "y": -1.525880907138344e-05, + "z": -1.525880907138344e-05 + }, + "emitRate": 2, + "emitSpeed": 0, + "emitterShouldTrail": true, + "id": "{e62ced49-fa18-4ae1-977f-abef5bc0f3ba}", + "lastEdited": 1536107948775366, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 40, + "name": "Rays", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.75, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 3.814434051513672, + "green": 1.2890124320983887, + "red": 1.2254328727722168, + "x": 1.2254328727722168, + "y": 1.2890124320983887, + "z": 3.814434051513672 + }, + "queryAACube": { + "scale": 4.330127239227295, + "x": -0.9396307468414307, + "y": -0.8760511875152588, + "z": 1.6493704319000244 + }, + "radiusFinish": 0.10000000149011612, + "radiusStart": 0, + "rotation": { + "w": 0.9594720602035522, + "x": -1.52587890625e-05, + "y": 0.28178834915161133, + "z": -4.57763671875e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/stripe.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 0.25, + "clientOnly": false, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255, + "x": 255, + "y": 255, + "z": 255 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 13.24000072479248, + "green": 13.24000072479248, + "red": 13.24000072479248, + "x": 13.24000072479248, + "y": 13.24000072479248, + "z": 13.24000072479248 + }, + "emitAcceleration": { + "blue": 0, + "green": 0.10000000149011612, + "red": 0, + "x": 0, + "y": 0.10000000149011612, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "emitRate": 6, + "emitSpeed": 0, + "id": "{298c0571-cbd8-487b-8640-64037d6a8414}", + "lastEdited": 1536107948776382, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 10, + "name": "Stars", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.07000000029802322, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 1.3712034225463867, + "green": 0.3698839843273163, + "red": 2.6216418743133545, + "x": 2.6216418743133545, + "y": 0.3698839843273163, + "z": 1.3712034225463867 + }, + "queryAACube": { + "scale": 22.932353973388672, + "x": -8.844534873962402, + "y": -11.096293449401855, + "z": -10.09497356414795 + }, + "radiusFinish": 0, + "radiusStart": 0, + "rotation": { + "w": 0.9852597713470459, + "x": -1.52587890625e-05, + "y": -0.17106890678405762, + "z": -7.62939453125e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.1097896099090576, + "green": 0.04847164824604988, + "red": 1.458284616470337, + "x": 1.458284616470337, + "y": 0.04847164824604988, + "z": 2.1097896099090576 + }, + "id": "{6625dbb8-ff25-458d-a92e-644b58460604}", + "lastEdited": 1536107948776195, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal1.fbx", + "name": "Try Again", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 3.946338653564453, + "green": 0.09449335932731628, + "red": 1.594836711883545, + "x": 1.594836711883545, + "y": 0.09449335932731628, + "z": 3.946338653564453 + }, + "queryAACube": { + "scale": 2.5651814937591553, + "x": 0.3122459650039673, + "y": -1.188097357749939, + "z": 2.663747787475586 + }, + "rotation": { + "w": 0.8220492601394653, + "x": -1.52587890625e-05, + "y": 0.5693598985671997, + "z": -0.0001068115234375 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/tryAgainEntityScript.js", + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 0.06014331430196762, + "green": 2.582186460494995, + "red": 2.582186698913574, + "x": 2.582186698913574, + "y": 2.582186460494995, + "z": 0.06014331430196762 + }, + "id": "{dfe92dce-f09d-4e9e-b3ed-c68ecd4d476f}", + "lastEdited": 1536108160862286, + "lastEditedBy": "{4656d4a8-5e61-4230-ab34-2888d7945bd6}", + "modelURL": "", + "name": "Oops Dialog", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.45927095413208, + "green": 1.6763916015625, + "red": 0, + "x": 0, + "y": 1.6763916015625, + "z": 1.45927095413208 + }, + "queryAACube": { + "scale": 3.6522583961486816, + "x": -1.8261291980743408, + "y": -0.14973759651184082, + "z": -0.36685824394226074 + }, + "rotation": { + "w": 0.8684672117233276, + "x": -4.57763671875e-05, + "y": 0.4957197904586792, + "z": -7.62939453125e-05 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/oopsEntityScript.js", + "scriptTimestamp": 1536102551825, + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{144a8cf4-b0e8-489a-9403-d74d4dc4cb3e}", + "lastEdited": 1536107948775774, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "rightWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 0, + "green": 1.0061144828796387, + "red": 4.965089321136475, + "x": 4.965089321136475, + "y": 1.0061144828796387, + "z": 0 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -0.8756546974182129, + "y": -4.834629535675049, + "z": -5.8407440185546875 + }, + "rotation": { + "w": 0.8637980222702026, + "x": -4.57763671875e-05, + "y": 0.5038070678710938, + "z": -1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "collidesWith": "static,dynamic,kinematic,otherAvatar,", + "collisionMask": 23, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 1.159199833869934, + "green": 2.8062009811401367, + "red": 1.6216505765914917, + "x": 1.6216505765914917, + "y": 2.8062009811401367, + "z": 1.159199833869934 + }, + "id": "{37f53408-3d0c-42a5-9891-e6c40a227349}", + "lastEdited": 1536107948775010, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "name": "Back Zone", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.8632707595825195, + "green": 1.6500625610351562, + "red": 3.3211965560913086, + "x": 3.3211965560913086, + "y": 1.6500625610351562, + "z": 1.8632707595825195 + }, + "queryAACube": { + "scale": 3.4421300888061523, + "x": 1.6001315116882324, + "y": -0.07100248336791992, + "z": 0.14220571517944336 + }, + "rotation": { + "w": 0.9304176568984985, + "x": 0, + "y": -0.36650121212005615, + "z": 0 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/zoneBackEntityScript.js", + "shapeType": "box", + "type": "Zone", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "clientOnly": false, + "color": { + "blue": 0, + "green": 0, + "red": 0 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 11.117486953735352, + "green": 3.580313205718994, + "red": 0.20000000298023224, + "x": 0.20000000298023224, + "y": 3.580313205718994, + "z": 11.117486953735352 + }, + "id": "{aa6e680c-6750-4776-95bc-ef3118cace5c}", + "lastEdited": 1536107948775945, + "lastEditedBy": "{ce82d352-3002-44ae-9b76-66492989a1db}", + "locked": true, + "name": "frontWall", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 2.662257671356201, + "green": 1.0063786506652832, + "red": 1.4868733882904053, + "x": 1.4868733882904053, + "y": 1.0063786506652832, + "z": 2.662257671356201 + }, + "queryAACube": { + "scale": 11.681488037109375, + "x": -4.353870391845703, + "y": -4.834365367889404, + "z": -3.1784863471984863 + }, + "rotation": { + "w": 0.9666743278503418, + "x": -4.57763671875e-05, + "y": -0.2560006380081177, + "z": 1.52587890625e-05 + }, + "shape": "Cube", + "type": "Box", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}", + "visible": false + }, + { + "clientOnly": false, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 2.1097896099090576, + "green": 0.04847164824604988, + "red": 1.458284616470337, + "x": 1.458284616470337, + "y": 0.04847164824604988, + "z": 2.1097896099090576 + }, + "id": "{303631f1-04f3-42a6-b8a8-8dd4b65d1231}", + "lastEdited": 1536107948776513, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "locked": true, + "modelURL": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/portal2.fbx", + "name": "Back", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "position": { + "blue": 1.5835940837860107, + "green": 0.09449335932731628, + "red": 3.028078079223633, + "x": 3.028078079223633, + "y": 0.09449335932731628, + "z": 1.5835940837860107 + }, + "queryAACube": { + "scale": 2.5651814937591553, + "x": 1.7454873323440552, + "y": -1.188097357749939, + "z": 0.3010033369064331 + }, + "rotation": { + "w": 0.9084458351135254, + "x": -1.52587890625e-05, + "y": 0.4179598093032837, + "z": -0.0001068115234375 + }, + "script": "https://hifi-content.s3.amazonaws.com/wayne/404redirectionScripts/backEntityScript.js", + "scriptTimestamp": 1535751754379, + "shapeType": "static-mesh", + "type": "Model", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }, + { + "alpha": 0, + "alphaFinish": 0, + "alphaStart": 0.25, + "clientOnly": false, + "colorFinish": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255, + "x": 255, + "y": 255, + "z": 255 + }, + "created": "2018-09-05T00:40:03Z", + "dimensions": { + "blue": 13.24000072479248, + "green": 13.24000072479248, + "red": 13.24000072479248, + "x": 13.24000072479248, + "y": 13.24000072479248, + "z": 13.24000072479248 + }, + "emitAcceleration": { + "blue": 0, + "green": 0.10000000149011612, + "red": 0, + "x": 0, + "y": 0.10000000149011612, + "z": 0 + }, + "emitDimensions": { + "blue": 1, + "green": 1, + "red": 1, + "x": 1, + "y": 1, + "z": 1 + }, + "emitOrientation": { + "w": 1, + "x": -1.52587890625e-05, + "y": -1.52587890625e-05, + "z": -1.52587890625e-05 + }, + "emitRate": 6, + "emitSpeed": 0, + "emitterShouldTrail": true, + "id": "{8ded39e6-303c-48f2-be79-81b715cca9f7}", + "lastEdited": 1536107948777127, + "lastEditedBy": "{b5bba536-25e5-4b12-a1be-5c7cd196a06a}", + "lifespan": 10, + "locked": true, + "maxParticles": 10, + "name": "Stars", + "owningAvatarID": "{00000000-0000-0000-0000-000000000000}", + "particleRadius": 0.07000000029802322, + "polarFinish": 3.1415927410125732, + "position": { + "blue": 3.78922963142395, + "green": 0.3698839843273163, + "red": 1.1863799095153809, + "x": 1.1863799095153809, + "y": 0.3698839843273163, + "z": 3.78922963142395 + }, + "queryAACube": { + "scale": 22.932353973388672, + "x": -10.279796600341797, + "y": -11.096293449401855, + "z": -7.676947593688965 + }, + "radiusFinish": 0, + "radiusStart": 0, + "rotation": { + "w": 0.996429443359375, + "x": -1.52587890625e-05, + "y": -0.08442819118499756, + "z": -4.57763671875e-05 + }, + "speedSpread": 0, + "spinFinish": null, + "spinStart": null, + "textures": "http://hifi-content.s3.amazonaws.com/alexia/Models/Portal/star.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + } + ], + "Id": "{18abccad-2d57-4176-9d89-24dc424916f5}", + "Version": 93 +} diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp index 419382f2cb..400085a62a 100644 --- a/interface/src/AndroidHelper.cpp +++ b/interface/src/AndroidHelper.cpp @@ -10,6 +10,7 @@ // #include "AndroidHelper.h" #include +#include #include "Application.h" #if defined(qApp) @@ -18,6 +19,7 @@ #define qApp (static_cast(QCoreApplication::instance())) AndroidHelper::AndroidHelper() { + qRegisterMetaType("QAudio::Mode"); } AndroidHelper::~AndroidHelper() { @@ -56,3 +58,12 @@ void AndroidHelper::processURL(const QString &url) { qApp->acceptURL(url); } } + +void AndroidHelper::notifyHeadsetOn(bool pluggedIn) { +#if defined (Q_OS_ANDROID) + auto audioClient = DependencyManager::get(); + if (audioClient) { + QMetaObject::invokeMethod(audioClient.data(), "setHeadsetPluggedIn", Q_ARG(bool, pluggedIn)); + } +#endif +} diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h index 03d92f91d9..11b84e4025 100644 --- a/interface/src/AndroidHelper.h +++ b/interface/src/AndroidHelper.h @@ -29,6 +29,7 @@ public: void performHapticFeedback(int duration); void processURL(const QString &url); + void notifyHeadsetOn(bool pluggedIn); AndroidHelper(AndroidHelper const&) = delete; void operator=(AndroidHelper const&) = delete; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7f43c3d773..db89b0a9cd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -195,6 +195,7 @@ #include "ui/SnapshotAnimated.h" #include "ui/StandAloneJSConsole.h" #include "ui/Stats.h" +#include "ui/AnimStats.h" #include "ui/UpdateDialog.h" #include "ui/overlays/Overlays.h" #include "ui/DomainConnectionModel.h" @@ -727,6 +728,9 @@ static const QString STATE_SNAP_TURN = "SnapTurn"; static const QString STATE_ADVANCED_MOVEMENT_CONTROLS = "AdvancedMovement"; static const QString STATE_GROUNDED = "Grounded"; static const QString STATE_NAV_FOCUSED = "NavigationFocused"; +static const QString STATE_PLATFORM_WINDOWS = "PlatformWindows"; +static const QString STATE_PLATFORM_MAC = "PlatformMac"; +static const QString STATE_PLATFORM_ANDROID = "PlatformAndroid"; // Statically provided display and input plugins extern DisplayPluginList getDisplayPlugins(); @@ -910,7 +914,8 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR, STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_THIRD_PERSON, STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT, - STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED } }); + STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED, + STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID } }); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -1182,13 +1187,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo const DomainHandler& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, SIGNAL(domainURLChanged(QUrl)), SLOT(domainURLChanged(QUrl))); + connect(&domainHandler, SIGNAL(redirectToErrorDomainURL(QUrl)), SLOT(goToErrorDomainURL(QUrl))); connect(&domainHandler, &DomainHandler::domainURLChanged, [](QUrl domainURL){ setCrashAnnotation("domain", domainURL.toString().toStdString()); }); connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); connect(&domainHandler, SIGNAL(connectedToDomain(QUrl)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); - connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &Application::clearDomainAvatars); connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() { getOverlays().deleteOverlay(getTabletScreenID()); getOverlays().deleteOverlay(getTabletHomeButtonID()); @@ -1196,6 +1201,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); + nodeList->getDomainHandler().setErrorDomainURL(QUrl(REDIRECT_HIFI_ADDRESS)); + // We could clear ATP assets only when changing domains, but it's possible that the domain you are connected // to has gone down and switched to a new content set, so when you reconnect the cached ATP assets will no longer be valid. connect(&domainHandler, &DomainHandler::disconnectedFromDomain, DependencyManager::get().data(), &ScriptCache::clearATPScriptsFromCache); @@ -1637,7 +1644,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo audioClient->setMuted(!audioClient->isMuted()); } else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) { cycleCamera(); - } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) { + } else if (action == controller::toInt(controller::Action::CONTEXT_MENU) && !isInterstitialMode()) { toggleTabletUI(); } else if (action == controller::toInt(controller::Action::RETICLE_X)) { auto oldPos = getApplicationCompositor().getReticlePosition(); @@ -1684,6 +1691,27 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _applicationStateDevice->setInputVariant(STATE_NAV_FOCUSED, []() -> float { return DependencyManager::get()->navigationFocused() ? 1 : 0; }); + _applicationStateDevice->setInputVariant(STATE_PLATFORM_WINDOWS, []() -> float { +#if defined(Q_OS_WIN) + return 1; +#else + return 0; +#endif + }); + _applicationStateDevice->setInputVariant(STATE_PLATFORM_MAC, []() -> float { +#if defined(Q_OS_MAC) + return 1; +#else + return 0; +#endif + }); + _applicationStateDevice->setInputVariant(STATE_PLATFORM_ANDROID, []() -> float { +#if defined(Q_OS_ANDROID) + return 1; +#else + return 0; +#endif + }); // Setup the _keyboardMouseDevice, _touchscreenDevice, _touchscreenVirtualPadDevice and the user input mapper with the default bindings userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice()); @@ -1732,15 +1760,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Make sure we don't time out during slow operations at startup updateHeartbeat(); - QTimer* settingsTimer = new QTimer(); moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{ - connect(qApp, &Application::beforeAboutToQuit, [this, settingsTimer]{ - bool autoLogout = Setting::Handle(AUTO_LOGOUT_SETTING_NAME, false).get(); - if (autoLogout) { - auto accountManager = DependencyManager::get(); - accountManager->logout(); - } + // This needs to run on the settings thread, so we need to pass the `settingsTimer` as the + // receiver object, otherwise it will run on the application thread and trigger a warning + // about trying to kill the timer on the main thread. + connect(qApp, &Application::beforeAboutToQuit, settingsTimer, [this, settingsTimer]{ // Disconnect the signal from the save settings QObject::disconnect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings); // Stop the settings timer @@ -1842,6 +1867,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); EntityTree::setAddMaterialToEntityOperator([this](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) { + if (_aboutToQuit) { + return false; + } + // try to find the renderable auto renderable = getEntities()->renderableForEntityId(entityID); if (renderable) { @@ -1857,6 +1886,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return false; }); EntityTree::setRemoveMaterialFromEntityOperator([this](const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) { + if (_aboutToQuit) { + return false; + } + // try to find the renderable auto renderable = getEntities()->renderableForEntityId(entityID); if (renderable) { @@ -2222,6 +2255,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(this, &QCoreApplication::aboutToQuit, this, &Application::addAssetToWorldMessageClose); connect(&domainHandler, &DomainHandler::domainURLChanged, this, &Application::addAssetToWorldMessageClose); + connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &Application::addAssetToWorldMessageClose); updateSystemTabletMode(); @@ -2276,6 +2310,24 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground); connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground); AndroidHelper::instance().notifyLoadComplete(); +#else + static int CHECK_LOGIN_TIMER = 3000; + QTimer* checkLoginTimer = new QTimer(this); + checkLoginTimer->setInterval(CHECK_LOGIN_TIMER); + checkLoginTimer->setSingleShot(true); + connect(checkLoginTimer, &QTimer::timeout, this, [this]() { + auto accountManager = DependencyManager::get(); + auto dialogsManager = DependencyManager::get(); + if (!accountManager->isLoggedIn()) { + Setting::Handle{"loginDialogPoppedUp", false}.set(true); + dialogsManager->showLoginDialog(); + QJsonObject loginData = {}; + loginData["action"] = "login dialog shown"; + UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData); + } + }); + Setting::Handle{"loginDialogPoppedUp", false}.set(false); + checkLoginTimer->start(); #endif } @@ -2410,6 +2462,8 @@ void Application::onAboutToQuit() { // so its persisted explicitly here Setting::Handle{ ACTIVE_DISPLAY_PLUGIN_SETTING_NAME }.set(getActiveDisplayPlugin()->getName()); + Setting::Handle{"loginDialogPoppedUp", false}.set(false); + getActiveDisplayPlugin()->deactivate(); if (_autoSwitchDisplayModeSupportedHMDPlugin && _autoSwitchDisplayModeSupportedHMDPlugin->isSessionActive()) { @@ -2489,6 +2543,11 @@ void Application::cleanupBeforeQuit() { } DependencyManager::destroy(); + bool autoLogout = Setting::Handle(AUTO_LOGOUT_SETTING_NAME, false).get(); + if (autoLogout) { + DependencyManager::get()->removeAccountFromFile(); + } + _displayPlugin.reset(); PluginManager::getInstance()->shutdown(); @@ -2636,6 +2695,10 @@ Application::~Application() { void Application::initializeGL() { qCDebug(interfaceapp) << "Created Display Window."; +#ifdef DISABLE_QML + setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); +#endif + // initialize glut for shape drawing; Qt apparently initializes it on OS X if (_isGLInitialized) { return; @@ -2961,6 +3024,9 @@ void Application::initializeUi() { if (_window && _window->isFullScreen()) { setFullscreen(nullptr, true); } + + + setIsInterstitialMode(true); } @@ -3054,8 +3120,10 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { void Application::onDesktopRootItemCreated(QQuickItem* rootItem) { Stats::show(); + AnimStats::show(); auto surfaceContext = DependencyManager::get()->getSurfaceContext(); surfaceContext->setContextProperty("Stats", Stats::getInstance()); + surfaceContext->setContextProperty("AnimStats", AnimStats::getInstance()); #if !defined(Q_OS_ANDROID) auto offscreenUi = DependencyManager::get(); @@ -3437,6 +3505,18 @@ bool Application::isServerlessMode() const { return false; } +void Application::setIsInterstitialMode(bool interstitialMode) { + bool enableInterstitial = DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled(); + if (enableInterstitial) { + if (_interstitialMode != interstitialMode) { + _interstitialMode = interstitialMode; + + DependencyManager::get()->setAudioPaused(_interstitialMode); + DependencyManager::get()->setMyAvatarDataPacketsPaused(_interstitialMode); + } + } +} + void Application::setIsServerlessMode(bool serverlessDomain) { auto tree = getEntities()->getTree(); if (tree) { @@ -3444,9 +3524,9 @@ void Application::setIsServerlessMode(bool serverlessDomain) { } } -void Application::loadServerlessDomain(QUrl domainURL) { +void Application::loadServerlessDomain(QUrl domainURL, bool errorDomain) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL)); + QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL), Q_ARG(bool, errorDomain)); return; } @@ -3478,8 +3558,11 @@ void Application::loadServerlessDomain(QUrl domainURL) { } std::map namedPaths = tmpTree->getNamedPaths(); - nodeList->getDomainHandler().connectedToServerless(namedPaths); - + if (errorDomain) { + nodeList->getDomainHandler().loadedErrorDomain(namedPaths); + } else { + nodeList->getDomainHandler().connectedToServerless(namedPaths); + } _fullSceneReceivedCounter++; } @@ -3714,7 +3797,7 @@ void Application::keyPressEvent(QKeyEvent* event) { _controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface->isKeyCaptured(event)) { + if (_controllerScriptingInterface->isKeyCaptured(event) || isInterstitialMode()) { return; } @@ -4591,6 +4674,7 @@ void Application::idle() { checkChangeCursor(); Stats::getInstance()->updateStats(); + AnimStats::getInstance()->updateStats(); // Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing // details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing @@ -5034,8 +5118,9 @@ void Application::updateLOD(float deltaTime) const { float presentTime = getActiveDisplayPlugin()->getAveragePresentTime(); float engineRunTime = (float)(_renderEngine->getConfiguration().get()->getCPURunTime()); float gpuTime = getGPUContext()->getFrameTimerGPUAverage(); + float batchTime = getGPUContext()->getFrameTimerBatchAverage(); auto lodManager = DependencyManager::get(); - lodManager->setRenderTimes(presentTime, engineRunTime, gpuTime); + lodManager->setRenderTimes(presentTime, engineRunTime, batchTime, gpuTime); lodManager->autoAdjustLOD(deltaTime); } else { DependencyManager::get()->resetLODAdjust(); @@ -5499,6 +5584,7 @@ void Application::update(float deltaTime) { return; } + if (!_physicsEnabled) { if (!domainLoadingInProgress) { PROFILE_ASYNC_BEGIN(app, "Scene Loading", ""); @@ -5519,6 +5605,7 @@ void Application::update(float deltaTime) { // scene is ready to compute its collision shape. if (getMyAvatar()->isReadyForPhysics()) { _physicsEnabled = true; + setIsInterstitialMode(false); getMyAvatar()->updateMotionBehaviorFromMenu(); } } @@ -5598,7 +5685,7 @@ void Application::update(float deltaTime) { // Transfer the user inputs to the driveKeys // FIXME can we drop drive keys and just have the avatar read the action states directly? myAvatar->clearDriveKeys(); - if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) { + if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT && !isInterstitialMode()) { if (!_controllerScriptingInterface->areActionsCaptured() && _myCamera.getMode() != CAMERA_MODE_MIRROR) { myAvatar->setDriveKey(MyAvatar::TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z)); myAvatar->setDriveKey(MyAvatar::TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y)); @@ -5793,15 +5880,13 @@ void Application::update(float deltaTime) { auto t5 = std::chrono::high_resolution_clock::now(); workload::Timings timings(6); - timings[0] = (t4 - t0); - timings[1] = (t5 - t4); - timings[2] = (t4 - t3); - timings[3] = (t3 - t2); - timings[4] = (t2 - t1); - timings[5] = (t1 - t0); - + timings[0] = t1 - t0; // prePhysics entities + timings[1] = t2 - t1; // prePhysics avatars + timings[2] = t3 - t2; // stepPhysics + timings[3] = t4 - t3; // postPhysics + timings[4] = t5 - t4; // non-physical kinematics + timings[5] = workload::Timing_ns((int32_t)(NSECS_PER_SECOND * deltaTime)); // game loop duration _gameWorkload.updateSimulationTimings(timings); - } } } else { @@ -5829,9 +5914,7 @@ void Application::update(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::update()"); -#if !defined(Q_OS_ANDROID) updateLOD(deltaTime); -#endif // TODO: break these out into distinct perfTimers when they prove interesting { @@ -5918,7 +6001,7 @@ void Application::update(float deltaTime) { // send packet containing downstream audio stats to the AudioMixer { quint64 sinceLastNack = now - _lastSendDownstreamAudioStats; - if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) { + if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS && !isInterstitialMode()) { _lastSendDownstreamAudioStats = now; QMetaObject::invokeMethod(DependencyManager::get().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); @@ -5993,7 +6076,7 @@ void Application::updateRenderArgs(float deltaTime) { _viewFrustum.calculate(); } appRenderArgs._renderArgs = RenderArgs(_gpuContext, lodManager->getOctreeSizeScale(), - lodManager->getBoundaryLevelAdjust(), RenderArgs::DEFAULT_RENDER_MODE, + lodManager->getBoundaryLevelAdjust(), lodManager->getLODAngleHalfTan(), RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); appRenderArgs._renderArgs._scene = getMain3DScene(); @@ -6081,21 +6164,23 @@ void Application::updateRenderArgs(float deltaTime) { } void Application::queryAvatars() { - auto avatarPacket = NLPacket::create(PacketType::AvatarQuery); - auto destinationBuffer = reinterpret_cast(avatarPacket->getPayload()); - unsigned char* bufferStart = destinationBuffer; + if (!isInterstitialMode()) { + auto avatarPacket = NLPacket::create(PacketType::AvatarQuery); + auto destinationBuffer = reinterpret_cast(avatarPacket->getPayload()); + unsigned char* bufferStart = destinationBuffer; - uint8_t numFrustums = (uint8_t)_conicalViews.size(); - memcpy(destinationBuffer, &numFrustums, sizeof(numFrustums)); - destinationBuffer += sizeof(numFrustums); + uint8_t numFrustums = (uint8_t)_conicalViews.size(); + memcpy(destinationBuffer, &numFrustums, sizeof(numFrustums)); + destinationBuffer += sizeof(numFrustums); - for (const auto& view : _conicalViews) { - destinationBuffer += view.serialize(destinationBuffer); + for (const auto& view : _conicalViews) { + destinationBuffer += view.serialize(destinationBuffer); + } + + avatarPacket->setPayloadSize(destinationBuffer - bufferStart); + + DependencyManager::get()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); } - - avatarPacket->setPayloadSize(destinationBuffer - bufferStart); - - DependencyManager::get()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); } @@ -6272,6 +6357,7 @@ void Application::updateWindowTitle() const { auto nodeList = DependencyManager::get(); auto accountManager = DependencyManager::get(); + auto isInErrorState = nodeList->getDomainHandler().isInErrorState(); QString buildVersion = " - " + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build")) @@ -6279,14 +6365,19 @@ void Application::updateWindowTitle() const { QString loginStatus = accountManager->isLoggedIn() ? "" : " (NOT LOGGED IN)"; - QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)"; + QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" : + nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)"; QString username = accountManager->getAccountInfo().getUsername(); setCrashAnnotation("username", username.toStdString()); QString currentPlaceName; if (isServerlessMode()) { - currentPlaceName = "serverless: " + DependencyManager::get()->getDomainURL().toString(); + if (isInErrorState) { + currentPlaceName = "serverless: " + nodeList->getDomainHandler().getErrorDomainURL().toString(); + } else { + currentPlaceName = "serverless: " + DependencyManager::get()->getDomainURL().toString(); + } } else { currentPlaceName = DependencyManager::get()->getDomainURL().host(); if (currentPlaceName.isEmpty()) { @@ -6318,6 +6409,7 @@ void Application::clearDomainOctreeDetails() { qCDebug(interfaceapp) << "Clearing domain octree details..."; resetPhysicsReadyInformation(); + setIsInterstitialMode(true); _octreeServerSceneStats.withWriteLock([&] { _octreeServerSceneStats.clear(); @@ -6338,11 +6430,6 @@ void Application::clearDomainOctreeDetails() { getMyAvatar()->setAvatarEntityDataChanged(true); } -void Application::clearDomainAvatars() { - getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities - DependencyManager::get()->clearOtherAvatars(); -} - void Application::domainURLChanged(QUrl domainURL) { // disable physics until we have enough information about our new location to not cause craziness. resetPhysicsReadyInformation(); @@ -6353,6 +6440,16 @@ void Application::domainURLChanged(QUrl domainURL) { updateWindowTitle(); } +void Application::goToErrorDomainURL(QUrl errorDomainURL) { + // disable physics until we have enough information about our new location to not cause craziness. + resetPhysicsReadyInformation(); + setIsServerlessMode(errorDomainURL.scheme() != URL_SCHEME_HIFI); + if (isServerlessMode()) { + loadServerlessDomain(errorDomainURL, true); + } + updateWindowTitle(); +} + void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; @@ -6391,7 +6488,7 @@ void Application::nodeActivated(SharedNodePointer node) { _octreeQuery.incrementConnectionID(); } - if (node->getType() == NodeType::AudioMixer) { + if (node->getType() == NodeType::AudioMixer && !isInterstitialMode()) { DependencyManager::get()->negotiateAudioFormat(); } @@ -6411,8 +6508,10 @@ void Application::nodeActivated(SharedNodePointer node) { getMyAvatar()->markIdentityDataChanged(); getMyAvatar()->resetLastSent(); - // transmit a "sendAll" packet to the AvatarMixer we just connected to. - getMyAvatar()->sendAvatarDataPacket(true); + if (!isInterstitialMode()) { + // transmit a "sendAll" packet to the AvatarMixer we just connected to. + getMyAvatar()->sendAvatarDataPacket(true); + } } } @@ -6430,9 +6529,6 @@ void Application::nodeKilled(SharedNodePointer node) { } else if (node->getType() == NodeType::EntityServer) { // we lost an entity server, clear all of the domain octree details clearDomainOctreeDetails(); - } else if (node->getType() == NodeType::AvatarMixer) { - // our avatar mixer has gone away - clear the hash of avatars - DependencyManager::get()->clearOtherAvatars(); } else if (node->getType() == NodeType::AssetServer) { // asset server going away - check if we have the asset browser showing @@ -6845,6 +6941,9 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) { shortName = shortName.mid(startIndex, endIndex - startIndex); } +#ifdef DISABLE_QML + DependencyManager::get()->loadScript(scriptFilenameOrURL); +#else QString message = "Would you like to run this script:\n" + shortName; ModalDialogListener* dlg = OffscreenUi::asyncQuestion(getWindow(), "Run Script", message, QMessageBox::Yes | QMessageBox::No); @@ -6859,7 +6958,7 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) { } QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr); }); - +#endif return true; } @@ -7763,7 +7862,7 @@ float Application::getRenderResolutionScale() const { } void Application::notifyPacketVersionMismatch() { - if (!_notifiedPacketVersionMismatchThisDomain) { + if (!_notifiedPacketVersionMismatchThisDomain && !isInterstitialMode()) { _notifiedPacketVersionMismatchThisDomain = true; QString message = "The location you are visiting is running an incompatible server version.\n"; diff --git a/interface/src/Application.h b/interface/src/Application.h index ae7e686f35..eedbdb7622 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -224,6 +224,7 @@ public: void setHmdTabletBecomesToolbarSetting(bool value); bool getPreferStylusOverLaser() { return _preferStylusOverLaserSetting.get(); } void setPreferStylusOverLaser(bool value); + // FIXME: Remove setting completely or make available through JavaScript API? //bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); } bool getPreferAvatarFingerOverStylus() { return false; } @@ -304,6 +305,7 @@ public: void saveNextPhysicsStats(QString filename); bool isServerlessMode() const; + bool isInterstitialMode() const { return _interstitialMode; } void replaceDomainContent(const QString& url); @@ -312,6 +314,9 @@ public: Q_INVOKABLE void copyToClipboard(const QString& text); + int getOtherAvatarsReplicaCount() { return DependencyManager::get()->getReplicaCount(); } + void setOtherAvatarsReplicaCount(int count) { DependencyManager::get()->setReplicaCount(count); } + #if defined(Q_OS_ANDROID) void beforeEnterBackground(); void enterBackground(); @@ -328,6 +333,8 @@ signals: void uploadRequest(QString path); + void loginDialogPoppedUp(); + public slots: QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); @@ -335,6 +342,7 @@ public slots: bool importEntities(const QString& url); void updateThreadPoolCount() const; void updateSystemTabletMode(); + void goToErrorDomainURL(QUrl errorDomainURL); Q_INVOKABLE void loadDialog(); Q_INVOKABLE void loadScriptURLDialog() const; @@ -423,7 +431,8 @@ public slots: void setPreferredCursor(const QString& cursor); void setIsServerlessMode(bool serverlessDomain); - void loadServerlessDomain(QUrl domainURL); + void loadServerlessDomain(QUrl domainURL, bool errorDomain = false); + void setIsInterstitialMode(bool interstitialMode); void updateVerboseLogging(); @@ -434,7 +443,6 @@ private slots: void onDesktopRootContextCreated(QQmlContext* qmlContext); void showDesktop(); void clearDomainOctreeDetails(); - void clearDomainAvatars(); void onAboutToQuit(); void onPresent(quint32 frameCount); @@ -623,6 +631,7 @@ private: QHash _keysPressed; bool _enableProcessOctreeThread; + bool _interstitialMode { false }; OctreePacketProcessor _octreeProcessor; EntityEditPacketSender _entityEditSender; diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 4119b843e1..5c79bedc9a 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -145,20 +145,9 @@ void AvatarBookmarks::removeBookmark(const QString& bookmarkName) { emit bookmarkDeleted(bookmarkName); } -bool isWearableEntity(const EntityItemPointer& entity) { - return entity->isVisible() && (entity->getParentJointIndex() != INVALID_JOINT_INDEX || (entity->getType() == EntityTypes::Model && (std::static_pointer_cast(entity))->getRelayParentJoints())) - && (entity->getParentID() == DependencyManager::get()->getSessionUUID() || entity->getParentID() == DependencyManager::get()->getMyAvatar()->getSelfID()); -} - void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) { auto myAvatar = DependencyManager::get()->getMyAvatar(); - auto treeRenderer = DependencyManager::get(); - EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - myAvatar->removeAvatarEntities([&](const QUuid& entityID) { - auto entity = entityTree->findEntityByID(entityID); - return entity && isWearableEntity(entity); - }); - + myAvatar->removeWearableAvatarEntities(); addAvatarEntities(avatarEntities); } @@ -183,10 +172,7 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) { auto myAvatar = DependencyManager::get()->getMyAvatar(); auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - myAvatar->removeAvatarEntities([&](const QUuid& entityID) { - auto entity = entityTree->findEntityByID(entityID); - return entity && isWearableEntity(entity); - }); + myAvatar->removeWearableAvatarEntities(); const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); myAvatar->useFullAvatarURL(avatarUrl); qCDebug(interfaceapp) << "Avatar On " << avatarUrl; diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index 8deddbda82..e86061b090 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -11,16 +11,18 @@ #include "ConnectionMonitor.h" +#include "Application.h" #include "ui/DialogsManager.h" #include #include +#include #include // Because the connection monitor is created at startup, the time we wait on initial load // should be longer to allow the application to initialize. -static const int ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 10000; -static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000; +static const int ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 10000; +static const int REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 5000; void ConnectionMonitor::init() { // Connect to domain disconnected message @@ -30,23 +32,35 @@ void ConnectionMonitor::init() { connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &ConnectionMonitor::startTimer); connect(&domainHandler, &DomainHandler::connectedToDomain, this, &ConnectionMonitor::stopTimer); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ConnectionMonitor::stopTimer); + connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &ConnectionMonitor::stopTimer); + connect(this, &ConnectionMonitor::setRedirectErrorState, &domainHandler, &DomainHandler::setRedirectErrorState); _timer.setSingleShot(true); if (!domainHandler.isConnected()) { - _timer.start(ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); + _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } - connect(&_timer, &QTimer::timeout, this, []() { - qDebug() << "ConnectionMonitor: Showing connection failure window"; - DependencyManager::get()->setDomainConnectionFailureVisibility(true); + connect(&_timer, &QTimer::timeout, this, [this]() { + // set in a timeout error + bool enableInterstitial = DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled(); + if (enableInterstitial) { + qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; + emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, "", 5); + } else { + qDebug() << "ConnectionMonitor: Showing connection failure window"; + DependencyManager::get()->setDomainConnectionFailureVisibility(true); + } }); } void ConnectionMonitor::startTimer() { - _timer.start(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); + _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } void ConnectionMonitor::stopTimer() { _timer.stop(); - DependencyManager::get()->setDomainConnectionFailureVisibility(false); + bool enableInterstitial = DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled(); + if (!enableInterstitial) { + DependencyManager::get()->setDomainConnectionFailureVisibility(false); + } } diff --git a/interface/src/ConnectionMonitor.h b/interface/src/ConnectionMonitor.h index e3d393163b..2fda6ef7cd 100644 --- a/interface/src/ConnectionMonitor.h +++ b/interface/src/ConnectionMonitor.h @@ -15,6 +15,7 @@ #include #include +class QUrl; class QString; class ConnectionMonitor : public QObject { @@ -22,6 +23,9 @@ class ConnectionMonitor : public QObject { public: void init(); +signals: + void setRedirectErrorState(QUrl errorURL, QString reasonMessage = "", int reasonCode = -1, const QString& extraInfo = ""); + private slots: void startTimer(); void stopTimer(); @@ -30,4 +34,4 @@ private: QTimer _timer; }; -#endif // hifi_ConnectionMonitor_h \ No newline at end of file +#endif // hifi_ConnectionMonitor_h diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index da1f14c450..729bfd9e45 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -19,27 +19,15 @@ #include "ui/DialogsManager.h" #include "InterfaceLogging.h" +const float LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS; +const float LODManager::DEFAULT_HMD_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS; -Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS); -Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS); +Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS); +Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", LODManager::DEFAULT_HMD_LOD_DOWN_FPS); LODManager::LODManager() { } -float LODManager::getLODDecreaseFPS() const { - if (qApp->isHMDMode()) { - return getHMDLODDecreaseFPS(); - } - return getDesktopLODDecreaseFPS(); -} - -float LODManager::getLODIncreaseFPS() const { - if (qApp->isHMDMode()) { - return getHMDLODIncreaseFPS(); - } - return getDesktopLODIncreaseFPS(); -} - // We use a "time-weighted running average" of the maxRenderTime and compare it against min/max thresholds // to determine if we should adjust the level of detail (LOD). // @@ -48,79 +36,133 @@ float LODManager::getLODIncreaseFPS() const { // faster than the runningAverage is computed, the error between the value and its runningAverage will be // reduced by 1/e every timescale of real-time that passes. const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.08f; // sec -// -// Assuming the measured value is affected by logic invoked by the runningAverage bumping up against its -// thresholds, we expect the adjustment to introduce a step-function. We want the runningAverage to settle -// to the new value BEFORE we test it aginst its thresholds again. Hence we test on a period that is a few -// multiples of the running average timescale: -const uint64_t LOD_AUTO_ADJUST_PERIOD = 4 * (uint64_t)(LOD_ADJUST_RUNNING_AVG_TIMESCALE * (float)USECS_PER_MSEC); // usec -const float LOD_AUTO_ADJUST_DECREMENT_FACTOR = 0.8f; -const float LOD_AUTO_ADJUST_INCREMENT_FACTOR = 1.2f; +// batchTIme is always contained in presentTime. +// We favor using batchTime instead of presentTime as a representative value for rendering duration (on present thread) +// if batchTime + cushionTime < presentTime. +// since we are shooting for fps around 60, 90Hz, the ideal frames are around 10ms +// so we are picking a cushion time of 3ms +const float LOD_BATCH_TO_PRESENT_CUSHION_TIME = 3.0f; // msec -void LODManager::setRenderTimes(float presentTime, float engineRunTime, float gpuTime) { - _presentTime = presentTime; - _engineRunTime = engineRunTime; - _gpuTime = gpuTime; +void LODManager::setRenderTimes(float presentTime, float engineRunTime, float batchTime, float gpuTime) { + // Make sure the sampled time are positive values + _presentTime = std::max(0.0f, presentTime); + _engineRunTime = std::max(0.0f, engineRunTime); + _batchTime = std::max(0.0f, batchTime); + _gpuTime = std::max(0.0f, gpuTime); } void LODManager::autoAdjustLOD(float realTimeDelta) { - float maxRenderTime = glm::max(glm::max(_presentTime, _engineRunTime), _gpuTime); - // compute time-weighted running average maxRenderTime - // Note: we MUST clamp the blend to 1.0 for stability - float blend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; - _avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * maxRenderTime; // msec - if (!_automaticLODAdjust || _avgRenderTime == 0.0f) { - // early exit + + // The "render time" is the worse of: + // - engineRunTime: Time spent in the render thread in the engine producing the gpu::Frame N + // - batchTime: Time spent in the present thread processing the batches of gpu::Frame N+1 + // - presentTime: Time spent in the present thread between the last 2 swap buffers considered the total time to submit gpu::Frame N+1 + // - gpuTime: Time spent in the GPU executing the gpu::Frame N + 2 + + // But Present time is in reality synched with the monitor/display refresh rate, it s always longer than batchTime. + // So if batchTime is fast enough relative to presentTime we are using it, otherwise we are using presentTime. got it ? + auto presentTime = (_presentTime > _batchTime + LOD_BATCH_TO_PRESENT_CUSHION_TIME ? _batchTime + LOD_BATCH_TO_PRESENT_CUSHION_TIME : _presentTime); + float maxRenderTime = glm::max(glm::max(presentTime, _engineRunTime), _gpuTime); + + // maxRenderTime must be a realistic valid duration in order for the regulation to work correctly. + // We make sure it s a non zero positive value (1.0ms) under 1 sec + maxRenderTime = std::max(1.0f, std::min(maxRenderTime, (float)MSECS_PER_SECOND)); + + // realTimeDelta must be a realistic valid duration in order for the regulation to work correctly. + // We make sure it a positive value under 1 sec + // note that if real time delta is very small we will early exit to avoid division by zero + realTimeDelta = std::max(0.0f, std::min(realTimeDelta, 1.0f)); + + // compute time-weighted running average render time (now and smooth) + // We MUST clamp the blend between 0.0 and 1.0 for stability + float nowBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; + float smoothBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) ? realTimeDelta / (LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) : 1.0f; + + //Evaluate the running averages for the render time + // We must sanity check for the output average evaluated to be in a valid range to avoid issues + _nowRenderTime = (1.0f - nowBlend) * _nowRenderTime + nowBlend * maxRenderTime; // msec + _nowRenderTime = std::max(0.0f, std::min(_nowRenderTime, (float)MSECS_PER_SECOND)); + _smoothRenderTime = (1.0f - smoothBlend) * _smoothRenderTime + smoothBlend * maxRenderTime; // msec + _smoothRenderTime = std::max(0.0f, std::min(_smoothRenderTime, (float)MSECS_PER_SECOND)); + + // Early exit if not regulating or if the simulation or render times don't matter + if (!_automaticLODAdjust || realTimeDelta <= 0.0f || _nowRenderTime <= 0.0f || _smoothRenderTime <= 0.0f) { return; } - float oldOctreeSizeScale = _octreeSizeScale; - float currentFPS = (float)MSECS_PER_SECOND / _avgRenderTime; - uint64_t now = usecTimestampNow(); - if (currentFPS < getLODDecreaseFPS()) { - if (now > _decreaseFPSExpiry) { - _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - if (_octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { - _octreeSizeScale *= LOD_AUTO_ADJUST_DECREMENT_FACTOR; - if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; - } - emit LODDecreased(); - // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime - // to provide an FPS just above the decrease threshold. It will drift close to its - // true value after a few LOD_ADJUST_TIMESCALEs and we'll adjust again as necessary. - _avgRenderTime = (float)MSECS_PER_SECOND / (getLODDecreaseFPS() + 1.0f); - } - _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - } - _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - } else if (currentFPS > getLODIncreaseFPS()) { - if (now > _increaseFPSExpiry) { - _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { - if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; - } else { - _octreeSizeScale *= LOD_AUTO_ADJUST_INCREMENT_FACTOR; - } - if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; - } - emit LODIncreased(); - // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime - // to provide an FPS just below the increase threshold. It will drift close to its - // true value after a few LOD_ADJUST_TIMESCALEs and we'll adjust again as necessary. - _avgRenderTime = (float)MSECS_PER_SECOND / (getLODIncreaseFPS() - 1.0f); - } - _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - } - _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - } else { - _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; - _decreaseFPSExpiry = _increaseFPSExpiry; + // Previous values for output + float oldOctreeSizeScale = getOctreeSizeScale(); + float oldLODAngle = getLODAngleDeg(); + + // Target fps is slightly overshooted by 5hz + float targetFPS = getLODTargetFPS() + LOD_OFFSET_FPS; + + // Current fps based on latest measurments + float currentNowFPS = (float)MSECS_PER_SECOND / _nowRenderTime; + float currentSmoothFPS = (float)MSECS_PER_SECOND / _smoothRenderTime; + + // Compute the Variance of the FPS signal (FPS - smouthFPS)^2 + // Also scale it by a percentage for fine tuning (default is 100%) + float currentVarianceFPS = (currentSmoothFPS - currentNowFPS); + currentVarianceFPS *= currentVarianceFPS; + currentVarianceFPS *= _pidCoefs.w; + + // evaluate current error between the current smoothFPS and target FPS + // and the sqaure of the error to compare against the Variance + auto currentErrorFPS = (targetFPS - currentSmoothFPS); + auto currentErrorFPSSquare = currentErrorFPS * currentErrorFPS; + + // Define a noiseCoef that is trying to adjust the error to the FPS target value based on its strength + // relative to the current Variance of the FPS signal. + // If the error is within the variance, just set to 0. + // if its within 2x the variance scale the control + // and full control if error is bigger than 2x variance + auto noiseCoef = 1.0f; + if (currentErrorFPSSquare < currentVarianceFPS) { + noiseCoef = 0.0f; + } else if (currentErrorFPSSquare < 2.0f * currentVarianceFPS) { + noiseCoef = (currentErrorFPSSquare - currentVarianceFPS) / currentVarianceFPS; } + // The final normalized error is the the error to the FPS target, weighted by the noiseCoef, then normailzed by the target FPS. + // it s also clamped in the [-1, 1] range + auto error = noiseCoef * currentErrorFPS / targetFPS; + error = glm::clamp(error, -1.0f, 1.0f); + + // Now we are getting into the P.I.D. controler code + // retreive the history of pid error and integral + auto previous_error = _pidHistory.x; + auto previous_integral = _pidHistory.y; + + // The dt used for temporal values of the controller is the current realTimedelta + // clamped to a reasonable granularity to make sure we are not over reacting + auto dt = std::min(realTimeDelta, LOD_ADJUST_RUNNING_AVG_TIMESCALE); + + // Compute the current integral and clamp to avoid accumulation + auto integral = previous_integral + error * dt; + glm::clamp(integral, -1.0f, 1.0f); + + // Compute derivative + // dt is never zero because realTimeDelta would have early exit above, but if it ever was let's zero the derivative term + auto derivative = (dt <= 0.0f ? 0.0f : (error - previous_error) / dt); + + // remember history + _pidHistory.x = error; + _pidHistory.y = integral; + _pidHistory.z = derivative; + + // Compute the output of the PID and record intermediate results for tuning + _pidOutputs.x = _pidCoefs.x * error; // Kp * error + _pidOutputs.y = _pidCoefs.y * integral; // Ki * integral + _pidOutputs.z = _pidCoefs.z * derivative; // Kd * derivative + + auto output = _pidOutputs.x + _pidOutputs.y + _pidOutputs.z; + _pidOutputs.w = output; + + // And now add the output of the controller to the LODAngle where we will guarantee it is in the proper range + setLODAngleDeg(oldLODAngle + output); + if (oldOctreeSizeScale != _octreeSizeScale) { auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog(); if (lodToolsDialog) { @@ -129,97 +171,96 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { } } +float LODManager::getLODAngleHalfTan() const { + return getPerspectiveAccuracyAngleTan(_octreeSizeScale, _boundaryLevelAdjust); +} +float LODManager::getLODAngle() const { + return 2.0f * atanf(getLODAngleHalfTan()); +} +float LODManager::getLODAngleDeg() const { + return glm::degrees(getLODAngle()); +} + +void LODManager::setLODAngleDeg(float lodAngle) { + auto newSolidAngle = std::max(0.5f, std::min(lodAngle, 90.f)); + auto halTan = glm::tan(glm::radians(newSolidAngle * 0.5f)); + auto octreeSizeScale = TREE_SCALE * OCTREE_TO_MESH_RATIO / halTan; + setOctreeSizeScale(octreeSizeScale); +} + +void LODManager::setSmoothScale(float t) { + _smoothScale = glm::max(1.0f, t); +} + +float LODManager::getPidKp() const { + return _pidCoefs.x; +} +float LODManager::getPidKi() const { + return _pidCoefs.y; +} +float LODManager::getPidKd() const { + return _pidCoefs.z; +} +float LODManager::getPidKv() const { + return _pidCoefs.w; +} +void LODManager::setPidKp(float k) { + _pidCoefs.x = k; +} +void LODManager::setPidKi(float k) { + _pidCoefs.y = k; +} +void LODManager::setPidKd(float k) { + _pidCoefs.z = k; +} +void LODManager::setPidKv(float t) { + _pidCoefs.w = t; +} + +float LODManager::getPidOp() const { + return _pidOutputs.x; +} +float LODManager::getPidOi() const { + return _pidOutputs.y; +} +float LODManager::getPidOd() const { + return _pidOutputs.z; +} +float LODManager::getPidO() const { + return _pidOutputs.w; +} + void LODManager::resetLODAdjust() { - _decreaseFPSExpiry = _increaseFPSExpiry = usecTimestampNow() + LOD_AUTO_ADJUST_PERIOD; } -float LODManager::getLODLevel() const { - // simpleLOD is a linearized and normalized number that represents how much LOD is being applied. - // It ranges from: - // 1.0 = normal (max) level of detail - // 0.0 = min level of detail - // In other words: as LOD "drops" the value of simpleLOD will also "drop", and it cannot go lower than 0.0. - const float LOG_MIN_LOD_RATIO = logf(ADJUST_LOD_MIN_SIZE_SCALE / ADJUST_LOD_MAX_SIZE_SCALE); - float power = logf(_octreeSizeScale / ADJUST_LOD_MAX_SIZE_SCALE); - float simpleLOD = (LOG_MIN_LOD_RATIO - power) / LOG_MIN_LOD_RATIO; - return simpleLOD; -} - -const float MIN_DECREASE_FPS = 0.5f; - -void LODManager::setDesktopLODDecreaseFPS(float fps) { - if (fps < MIN_DECREASE_FPS) { - // avoid divide by zero - fps = MIN_DECREASE_FPS; - } - _desktopMaxRenderTime = (float)MSECS_PER_SECOND / fps; -} - -float LODManager::getDesktopLODDecreaseFPS() const { - return (float)MSECS_PER_SECOND / _desktopMaxRenderTime; -} - -float LODManager::getDesktopLODIncreaseFPS() const { - return glm::min(((float)MSECS_PER_SECOND / _desktopMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_DESKTOP_FPS); -} - -void LODManager::setHMDLODDecreaseFPS(float fps) { - if (fps < MIN_DECREASE_FPS) { - // avoid divide by zero - fps = MIN_DECREASE_FPS; - } - _hmdMaxRenderTime = (float)MSECS_PER_SECOND / fps; -} - -float LODManager::getHMDLODDecreaseFPS() const { - return (float)MSECS_PER_SECOND / _hmdMaxRenderTime; -} - -float LODManager::getHMDLODIncreaseFPS() const { - return glm::min(((float)MSECS_PER_SECOND / _hmdMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_HMD_FPS); -} - -QString LODManager::getLODFeedbackText() { - // determine granularity feedback - int boundaryLevelAdjust = getBoundaryLevelAdjust(); - QString granularityFeedback; - switch (boundaryLevelAdjust) { - case 0: { - granularityFeedback = QString("."); - } break; - case 1: { - granularityFeedback = QString(" at half of standard granularity."); - } break; - case 2: { - granularityFeedback = QString(" at a third of standard granularity."); - } break; - default: { - granularityFeedback = QString(" at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1); - } break; - } - // distance feedback - float octreeSizeScale = getOctreeSizeScale(); - float relativeToDefault = octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE; - int relativeToTwentyTwenty = 20 / relativeToDefault; - - QString result; - if (relativeToDefault > 1.01f) { - result = QString("20:%1 or %2 times further than average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault,0,'f',2).arg(granularityFeedback); - } else if (relativeToDefault > 0.99f) { - result = QString("20:20 or the default distance for average vision%1").arg(granularityFeedback); - } else if (relativeToDefault > 0.01f) { - result = QString("20:%1 or %2 of default distance for average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault,0,'f',3).arg(granularityFeedback); - } else { - result = QString("%2 of default distance for average vision%3").arg(relativeToDefault,0,'f',3).arg(granularityFeedback); - } - return result; +void LODManager::setAutomaticLODAdjust(bool value) { + _automaticLODAdjust = value; + emit autoLODChanged(); } bool LODManager::shouldRender(const RenderArgs* args, const AABox& bounds) { - // FIXME - eventually we want to use the render accuracy as an indicator for the level of detail - // to use in rendering. - float renderAccuracy = calculateRenderAccuracy(args->getViewFrustum().getPosition(), bounds, args->_sizeScale, args->_boundaryLevelAdjust); - return (renderAccuracy > 0.0f); + // To decide if the bound should be rendered or not at the specified Args->lodAngle, + // we need to compute the apparent angle of the bound from the frustum origin, + // and compare it against the lodAngle, if it is greater or equal we should render the content of that bound. + // we abstract the bound as a sphere centered on the bound center and of radius half diagonal of the bound. + + // Instead of comparing angles, we are comparing the tangent of the half angle which are more efficient to compute: + // we are comparing the square of the half tangent apparent angle for the bound against the LODAngle Half tangent square + // if smaller, the bound is too small and we should NOT render it, return true otherwise. + + // Tangent Adjacent side is eye to bound center vector length + auto pos = args->getViewFrustum().getPosition() - bounds.calcCenter(); + auto halfTanAdjacentSq = glm::dot(pos, pos); + + // Tangent Opposite side is the half length of the dimensions vector of the bound + auto dim = bounds.getDimensions(); + auto halfTanOppositeSq = 0.25f * glm::dot(dim, dim); + + // The test is: + // isVisible = halfTanSq >= lodHalfTanSq = (halfTanOppositeSq / halfTanAdjacentSq) >= lodHalfTanSq + // which we express as below to avoid division + // (halfTanOppositeSq) >= lodHalfTanSq * halfTanAdjacentSq + return (halfTanOppositeSq >= args->_lodAngleHalfTanSq * halfTanAdjacentSq); }; void LODManager::setOctreeSizeScale(float sizeScale) { @@ -230,13 +271,140 @@ void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; } +QString LODManager::getLODFeedbackText() { + // determine granularity feedback + int boundaryLevelAdjust = getBoundaryLevelAdjust(); + QString granularityFeedback; + switch (boundaryLevelAdjust) { + case 0: { + granularityFeedback = QString("."); + } break; + case 1: { + granularityFeedback = QString(" at half of standard granularity."); + } break; + case 2: { + granularityFeedback = QString(" at a third of standard granularity."); + } break; + default: { + granularityFeedback = QString(" at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1); + } break; + } + // distance feedback + float octreeSizeScale = getOctreeSizeScale(); + float relativeToDefault = octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE; + int relativeToTwentyTwenty = 20 / relativeToDefault; + + QString result; + if (relativeToDefault > 1.01f) { + result = QString("20:%1 or %2 times further than average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault, 0, 'f', 2).arg(granularityFeedback); + } else if (relativeToDefault > 0.99f) { + result = QString("20:20 or the default distance for average vision%1").arg(granularityFeedback); + } else if (relativeToDefault > 0.01f) { + result = QString("20:%1 or %2 of default distance for average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault, 0, 'f', 3).arg(granularityFeedback); + } else { + result = QString("%2 of default distance for average vision%3").arg(relativeToDefault, 0, 'f', 3).arg(granularityFeedback); + } + return result; +} + void LODManager::loadSettings() { - setDesktopLODDecreaseFPS(desktopLODDecreaseFPS.get()); - setHMDLODDecreaseFPS(hmdLODDecreaseFPS.get()); + setDesktopLODTargetFPS(desktopLODDecreaseFPS.get()); + setHMDLODTargetFPS(hmdLODDecreaseFPS.get()); } void LODManager::saveSettings() { - desktopLODDecreaseFPS.set(getDesktopLODDecreaseFPS()); - hmdLODDecreaseFPS.set(getHMDLODDecreaseFPS()); + desktopLODDecreaseFPS.set(getDesktopLODTargetFPS()); + hmdLODDecreaseFPS.set(getHMDLODTargetFPS()); } +const float MIN_DECREASE_FPS = 0.5f; + +void LODManager::setDesktopLODTargetFPS(float fps) { + if (fps < MIN_DECREASE_FPS) { + // avoid divide by zero + fps = MIN_DECREASE_FPS; + } + _desktopTargetFPS = fps; +} + +float LODManager::getDesktopLODTargetFPS() const { + return _desktopTargetFPS; +} + +void LODManager::setHMDLODTargetFPS(float fps) { + if (fps < MIN_DECREASE_FPS) { + // avoid divide by zero + fps = MIN_DECREASE_FPS; + } + _hmdTargetFPS = fps; +} + +float LODManager::getHMDLODTargetFPS() const { + return _hmdTargetFPS; +} + +float LODManager::getLODTargetFPS() const { + if (qApp->isHMDMode()) { + return getHMDLODTargetFPS(); + } + return getDesktopLODTargetFPS(); +} + +void LODManager::setWorldDetailQuality(float quality) { + static const float MIN_FPS = 10; + static const float LOW = 0.25f; + + bool isLowestValue = quality == LOW; + bool isHMDMode = qApp->isHMDMode(); + + float maxFPS = isHMDMode ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS; + float desiredFPS = maxFPS; + + if (!isLowestValue) { + float calculatedFPS = (maxFPS - (maxFPS * quality)); + desiredFPS = calculatedFPS < MIN_FPS ? MIN_FPS : calculatedFPS; + } + + if (isHMDMode) { + setHMDLODTargetFPS(desiredFPS); + } else { + setDesktopLODTargetFPS(desiredFPS); + } + + emit worldDetailQualityChanged(); +} + +float LODManager::getWorldDetailQuality() const { + + static const float LOW = 0.25f; + static const float MEDIUM = 0.5f; + static const float HIGH = 0.75f; + + bool inHMD = qApp->isHMDMode(); + + float targetFPS = 0.0f; + if (inHMD) { + targetFPS = getHMDLODTargetFPS(); + } else { + targetFPS = getDesktopLODTargetFPS(); + } + float maxFPS = inHMD ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS; + float percentage = 1.0f - targetFPS / maxFPS; + + if (percentage <= LOW) { + return LOW; + } else if (percentage <= MEDIUM) { + return MEDIUM; + } + + return HIGH; +} + + +void LODManager::setLODQualityLevel(float quality) { + _lodQualityLevel = quality; +} + +float LODManager::getLODQualityLevel() const { + return _lodQualityLevel; +} diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 8f88da63a8..6206fd3539 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -19,18 +19,14 @@ #include #include -const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f; -const float DEFAULT_HMD_LOD_DOWN_FPS = 34.0f; -const float DEFAULT_DESKTOP_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_DESKTOP_LOD_DOWN_FPS; // msec -const float DEFAULT_HMD_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_HMD_LOD_DOWN_FPS; // msec -const float MAX_LIKELY_DESKTOP_FPS = 59.0f; // this is essentially, V-synch - 1 fps -const float MAX_LIKELY_HMD_FPS = 74.0f; // this is essentially, V-synch - 1 fps -const float INCREASE_LOD_GAP_FPS = 10.0f; // fps - -// The default value DEFAULT_OCTREE_SIZE_SCALE means you can be 400 meters away from a 1 meter object in order to see it (which is ~20:20 vision). -const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE; -// This controls how low the auto-adjust LOD will go. We want a minimum vision of ~20:500 or 0.04 of default -const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.04f; +#ifdef Q_OS_ANDROID +const float LOD_DEFAULT_QUALITY_LEVEL = 0.75f; // default quality level setting is High (lower framerate) +#else +const float LOD_DEFAULT_QUALITY_LEVEL = 0.5f; // default quality level setting is Mid +#endif +const float LOD_MAX_LIKELY_DESKTOP_FPS = 60.0f; // this is essentially, V-synch fps +const float LOD_MAX_LIKELY_HMD_FPS = 90.0f; // this is essentially, V-synch fps +const float LOD_OFFSET_FPS = 5.0f; // offset of FPS to add for computing the target framerate class AABox; @@ -53,24 +49,47 @@ class AABox; class LODManager : public QObject, public Dependency { Q_OBJECT - SINGLETON_DEPENDENCY + SINGLETON_DEPENDENCY - Q_PROPERTY(float presentTime READ getPresentTime) - Q_PROPERTY(float engineRunTime READ getEngineRunTime) - Q_PROPERTY(float gpuTime READ getGPUTime) - Q_PROPERTY(float avgRenderTime READ getAverageRenderTime) - Q_PROPERTY(float fps READ getMaxTheoreticalFPS) - Q_PROPERTY(float lodLevel READ getLODLevel) - Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) - Q_PROPERTY(float lodIncreaseFPS READ getLODIncreaseFPS) + Q_PROPERTY(float worldDetailQuality READ getWorldDetailQuality WRITE setWorldDetailQuality NOTIFY worldDetailQualityChanged) + + Q_PROPERTY(float lodQualityLevel READ getLODQualityLevel WRITE setLODQualityLevel NOTIFY lodQualityLevelChanged) + + Q_PROPERTY(bool automaticLODAdjust READ getAutomaticLODAdjust WRITE setAutomaticLODAdjust NOTIFY autoLODChanged) + + Q_PROPERTY(float presentTime READ getPresentTime) + Q_PROPERTY(float engineRunTime READ getEngineRunTime) + Q_PROPERTY(float batchTime READ getBatchTime) + Q_PROPERTY(float gpuTime READ getGPUTime) + + Q_PROPERTY(float nowRenderTime READ getNowRenderTime) + Q_PROPERTY(float nowRenderFPS READ getNowRenderFPS) + + Q_PROPERTY(float smoothScale READ getSmoothScale WRITE setSmoothScale) + Q_PROPERTY(float smoothRenderTime READ getSmoothRenderTime) + Q_PROPERTY(float smoothRenderFPS READ getSmoothRenderFPS) + + Q_PROPERTY(float lodTargetFPS READ getLODTargetFPS) + + Q_PROPERTY(float lodAngleDeg READ getLODAngleDeg WRITE setLODAngleDeg) + + Q_PROPERTY(float pidKp READ getPidKp WRITE setPidKp) + Q_PROPERTY(float pidKi READ getPidKi WRITE setPidKi) + Q_PROPERTY(float pidKd READ getPidKd WRITE setPidKd) + Q_PROPERTY(float pidKv READ getPidKv WRITE setPidKv) + + Q_PROPERTY(float pidOp READ getPidOp) + Q_PROPERTY(float pidOi READ getPidOi) + Q_PROPERTY(float pidOd READ getPidOd) + Q_PROPERTY(float pidO READ getPidO) public: - + /**jsdoc * @function LODManager.setAutomaticLODAdjust * @param {boolean} value */ - Q_INVOKABLE void setAutomaticLODAdjust(bool value) { _automaticLODAdjust = value; } + Q_INVOKABLE void setAutomaticLODAdjust(bool value); /**jsdoc * @function LODManager.getAutomaticLODAdjust @@ -79,42 +98,31 @@ public: Q_INVOKABLE bool getAutomaticLODAdjust() const { return _automaticLODAdjust; } /**jsdoc - * @function LODManager.setDesktopLODDecreaseFPS + * @function LODManager.setDesktopLODTargetFPS * @param {number} value */ - Q_INVOKABLE void setDesktopLODDecreaseFPS(float value); + Q_INVOKABLE void setDesktopLODTargetFPS(float value); /**jsdoc - * @function LODManager.getDesktopLODDecreaseFPS + * @function LODManager.getDesktopLODTargetFPS * @returns {number} */ - Q_INVOKABLE float getDesktopLODDecreaseFPS() const; + Q_INVOKABLE float getDesktopLODTargetFPS() const; /**jsdoc - * @function LODManager.getDesktopLODIncreaseFPS - * @returns {number} - */ - Q_INVOKABLE float getDesktopLODIncreaseFPS() const; - - /**jsdoc - * @function LODManager.setHMDLODDecreaseFPS + * @function LODManager.setHMDLODTargetFPS * @param {number} value */ - - Q_INVOKABLE void setHMDLODDecreaseFPS(float value); + + Q_INVOKABLE void setHMDLODTargetFPS(float value); /**jsdoc - * @function LODManager.getHMDLODDecreaseFPS + * @function LODManager.getHMDLODTargetFPS * @returns {number} */ - Q_INVOKABLE float getHMDLODDecreaseFPS() const; + Q_INVOKABLE float getHMDLODTargetFPS() const; - /**jsdoc - * @function LODManager.getHMDLODIncreaseFPS - * @returns {number} - */ - Q_INVOKABLE float getHMDLODIncreaseFPS() const; // User Tweakable LOD Items /**jsdoc @@ -148,32 +156,61 @@ public: Q_INVOKABLE int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } /**jsdoc - * @function LODManager.getLODDecreaseFPS - * @returns {number} - */ - Q_INVOKABLE float getLODDecreaseFPS() const; + * @function LODManager.getLODTargetFPS + * @returns {number} + */ + Q_INVOKABLE float getLODTargetFPS() const; - /**jsdoc - * @function LODManager.getLODIncreaseFPS - * @returns {number} - */ - Q_INVOKABLE float getLODIncreaseFPS() const; float getPresentTime() const { return _presentTime; } float getEngineRunTime() const { return _engineRunTime; } + float getBatchTime() const { return _batchTime; } float getGPUTime() const { return _gpuTime; } static bool shouldRender(const RenderArgs* args, const AABox& bounds); - void setRenderTimes(float presentTime, float engineRunTime, float gpuTime); + void setRenderTimes(float presentTime, float engineRunTime, float batchTime, float gpuTime); void autoAdjustLOD(float realTimeDelta); void loadSettings(); void saveSettings(); void resetLODAdjust(); - float getAverageRenderTime() const { return _avgRenderTime; }; - float getMaxTheoreticalFPS() const { return (float)MSECS_PER_SECOND / _avgRenderTime; }; - float getLODLevel() const; + float getNowRenderTime() const { return _nowRenderTime; }; + float getNowRenderFPS() const { return (_nowRenderTime > 0.0f ? (float)MSECS_PER_SECOND / _nowRenderTime : 0.0f); }; + + void setSmoothScale(float t); + float getSmoothScale() const { return _smoothScale; } + + float getSmoothRenderTime() const { return _smoothRenderTime; }; + float getSmoothRenderFPS() const { return (_smoothRenderTime > 0.0f ? (float)MSECS_PER_SECOND / _smoothRenderTime : 0.0f); }; + + void setWorldDetailQuality(float quality); + float getWorldDetailQuality() const; + + void setLODQualityLevel(float quality); + float getLODQualityLevel() const; + + float getLODAngleDeg() const; + void setLODAngleDeg(float lodAngle); + float getLODAngleHalfTan() const; + float getLODAngle() const; + + float getPidKp() const; + float getPidKi() const; + float getPidKd() const; + float getPidKv() const; + void setPidKp(float k); + void setPidKi(float k); + void setPidKd(float k); + void setPidKv(float t); + + float getPidOp() const; + float getPidOi() const; + float getPidOd() const; + float getPidO() const; + + static const float DEFAULT_DESKTOP_LOD_DOWN_FPS; + static const float DEFAULT_HMD_LOD_DOWN_FPS; signals: @@ -189,22 +226,35 @@ signals: */ void LODDecreased(); + void autoLODChanged(); + void lodQualityLevelChanged(); + void worldDetailQualityChanged(); + private: LODManager(); bool _automaticLODAdjust = true; - float _presentTime { 0.0f }; // msec - float _engineRunTime { 0.0f }; // msec - float _gpuTime { 0.0f }; // msec - float _avgRenderTime { 0.0f }; // msec - float _desktopMaxRenderTime { DEFAULT_DESKTOP_MAX_RENDER_TIME }; - float _hmdMaxRenderTime { DEFAULT_HMD_MAX_RENDER_TIME }; + + float _presentTime{ 0.0f }; // msec + float _engineRunTime{ 0.0f }; // msec + float _batchTime{ 0.0f }; // msec + float _gpuTime{ 0.0f }; // msec + + float _nowRenderTime{ 0.0f }; // msec + float _smoothScale{ 10.0f }; // smooth is evaluated over 10 times longer than now + float _smoothRenderTime{ 0.0f }; // msec + + float _lodQualityLevel{ LOD_DEFAULT_QUALITY_LEVEL }; + + float _desktopTargetFPS { LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS }; + float _hmdTargetFPS { LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS }; float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; int _boundaryLevelAdjust = 0; - uint64_t _decreaseFPSExpiry { 0 }; - uint64_t _increaseFPSExpiry { 0 }; + glm::vec4 _pidCoefs{ 1.0f, 0.0f, 0.0f, 1.0f }; // Kp, Ki, Kd, Kv + glm::vec4 _pidHistory{ 0.0f }; + glm::vec4 _pidOutputs{ 0.0f }; }; #endif // hifi_LODManager_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index fe7083ea30..7a310e675f 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -46,6 +46,7 @@ #include "InterfaceLogging.h" #include "LocationBookmarks.h" #include "DeferredLightingEffect.h" +#include "PickManager.h" #include "AmbientOcclusionEffect.h" #include "RenderShadowTask.h" @@ -254,7 +255,7 @@ Menu::Menu() { connect(action, &QAction::triggered, [] { auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); auto hmd = DependencyManager::get(); - tablet->loadQMLSource("hifi/tablet/ControllerSettings.qml"); + tablet->pushOntoStack("hifi/tablet/ControllerSettings.qml"); if (!hmd->getShouldShowTablet()) { hmd->toggleShouldShowTablet(); @@ -451,6 +452,9 @@ Menu::Menu() { }); } + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ComputeBlendshapes, 0, true, + DependencyManager::get().data(), SLOT(setComputeBlendshapes(bool))); + // Developer > Assets >>> // Menu item is not currently needed but code should be kept in case it proves useful again at some stage. //#define WANT_ASSET_MIGRATION @@ -688,6 +692,11 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraints, 0, false, qApp, SLOT(setShowBulletConstraints(bool))); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraintLimits, 0, false, qApp, SLOT(setShowBulletConstraintLimits(bool))); + // Developer > Picking >>> + MenuWrapper* pickingOptionsMenu = developerMenu->addMenu("Picking"); + addCheckableActionToQMenuAndActionHash(pickingOptionsMenu, MenuOption::ForceCoarsePicking, 0, false, + DependencyManager::get().data(), SLOT(setForceCoarsePicking(bool))); + // Developer > Display Crash Options addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true); // Developer > Crash >>> @@ -728,6 +737,7 @@ Menu::Menu() { // Developer > Stats addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); + addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AnimStats); // Settings > Enable Speech Control API #if defined(Q_OS_MAC) || defined(Q_OS_WIN) diff --git a/interface/src/Menu.h b/interface/src/Menu.h index c489a3f0b0..031ee2561c 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -197,6 +197,7 @@ namespace MenuOption { const QString SMIEyeTracking = "SMI Eye Tracking"; const QString SparseTextureManagement = "Enable Sparse Texture Management"; const QString Stats = "Show Statistics"; + const QString AnimStats = "Show Animation Stats"; const QString StopAllScripts = "Stop All Scripts"; const QString SuppressShortTimings = "Suppress Timings Less than 10ms"; const QString ThirdPerson = "Third Person"; @@ -221,6 +222,8 @@ namespace MenuOption { const QString NotificationSounds = "play_notification_sounds"; const QString NotificationSoundsSnapshot = "play_notification_sounds_snapshot"; const QString NotificationSoundsTablet = "play_notification_sounds_tablet"; + const QString ForceCoarsePicking = "Force Coarse Picking"; + const QString ComputeBlendshapes = "Compute Blendshapes"; } #endif // hifi_Menu_h diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 3112b6e8bd..0fdd246d7b 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -137,7 +137,7 @@ void AvatarManager::updateMyAvatar(float deltaTime) { quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; - if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS) { + if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) { // send head/hand data to the avatar mixer and voxel server PerformanceTimer perfTimer("send"); _myAvatar->sendAvatarDataPacket(); @@ -155,6 +155,16 @@ float AvatarManager::getAvatarDataRate(const QUuid& sessionID, const QString& ra return avatar ? avatar->getDataRate(rateName) : 0.0f; } +void AvatarManager::setMyAvatarDataPacketsPaused(bool pause) { + if (_myAvatarDataPacketsPaused != pause) { + _myAvatarDataPacketsPaused = pause; + + if (!_myAvatarDataPacketsPaused) { + _myAvatar->sendAvatarDataPacket(true); + } + } +} + float AvatarManager::getAvatarUpdateRate(const QUuid& sessionID, const QString& rateName) const { auto avatar = getAvatarBySessionID(sessionID); return avatar ? avatar->getUpdateRate(rateName) : 0.0f; @@ -166,58 +176,62 @@ float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QStri } void AvatarManager::updateOtherAvatars(float deltaTime) { - // lock the hash for read to check the size - QReadLocker lock(&_hashLock); - if (_avatarHash.size() < 2 && _avatarsToFade.isEmpty()) { - return; + { + // lock the hash for read to check the size + QReadLocker lock(&_hashLock); + if (_avatarHash.size() < 2 && _avatarsToFade.isEmpty()) { + return; + } } - lock.unlock(); PerformanceTimer perfTimer("otherAvatars"); class SortableAvatar: public PrioritySortUtil::Sortable { public: SortableAvatar() = delete; - SortableAvatar(const AvatarSharedPointer& avatar) : _avatar(avatar) {} + SortableAvatar(const std::shared_ptr& avatar) : _avatar(avatar) {} glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } - float getRadius() const override { return std::static_pointer_cast(_avatar)->getBoundingRadius(); } - uint64_t getTimestamp() const override { return std::static_pointer_cast(_avatar)->getLastRenderUpdateTime(); } - AvatarSharedPointer getAvatar() const { return _avatar; } + float getRadius() const override { return _avatar->getBoundingRadius(); } + uint64_t getTimestamp() const override { return _avatar->getLastRenderUpdateTime(); } + std::shared_ptr getAvatar() const { return _avatar; } private: - AvatarSharedPointer _avatar; + std::shared_ptr _avatar; }; + auto avatarMap = getHashCopy(); const auto& views = qApp->getConicalViews(); PrioritySortUtil::PriorityQueue sortedAvatars(views, AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge); + sortedAvatars.reserve(avatarMap.size() - 1); // don't include MyAvatar - // sort - auto avatarMap = getHashCopy(); + // Build vector and compute priorities + auto nodeList = DependencyManager::get(); AvatarHash::iterator itr = avatarMap.begin(); while (itr != avatarMap.end()) { const auto& avatar = std::static_pointer_cast(*itr); // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. // DO NOT update or fade out uninitialized Avatars - if (avatar != _myAvatar && avatar->isInitialized()) { + if (avatar != _myAvatar && avatar->isInitialized() && !nodeList->isPersonalMutingNode(avatar->getID())) { sortedAvatars.push(SortableAvatar(avatar)); } ++itr; } + // Sort + const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); // process in sorted order uint64_t startTime = usecTimestampNow(); - const uint64_t UPDATE_BUDGET = 2000; // usec - uint64_t updateExpiry = startTime + UPDATE_BUDGET; + uint64_t updateExpiry = startTime + MAX_UPDATE_AVATARS_TIME_BUDGET; int numAvatarsUpdated = 0; int numAVatarsNotUpdated = 0; render::Transaction renderTransaction; workload::Transaction workloadTransaction; - while (!sortedAvatars.empty()) { - const SortableAvatar& sortData = sortedAvatars.top(); + for (auto it = sortedAvatarVector.begin(); it != sortedAvatarVector.end(); ++it) { + const SortableAvatar& sortData = *it; const auto avatar = std::static_pointer_cast(sortData.getAvatar()); // TODO: to help us scale to more avatars it would be nice to not have to poll orb state here @@ -229,19 +243,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { avatar->updateOrbPosition(); } - bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); - if (ignoring) { - sortedAvatars.pop(); - continue; - } - // for ALL avatars... if (_shouldRender) { avatar->ensureInScene(avatar, qApp->getMain3DScene()); } avatar->animateScaleChanges(deltaTime); - const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY; uint64_t now = usecTimestampNow(); if (now < updateExpiry) { // we're within budget @@ -260,26 +267,19 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { // --> some avatar velocity measurements may be a little off // no time to simulate, but we take the time to count how many were tragically missed - bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD; - if (!inView) { - break; - } - if (inView && avatar->hasNewJointData()) { - numAVatarsNotUpdated++; - } - sortedAvatars.pop(); - while (inView && !sortedAvatars.empty()) { - const SortableAvatar& newSortData = sortedAvatars.top(); - const auto newAvatar = std::static_pointer_cast(newSortData.getAvatar()); - inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; - if (inView && newAvatar->hasNewJointData()) { - numAVatarsNotUpdated++; + while (it != sortedAvatarVector.end()) { + const SortableAvatar& newSortData = *it; + const auto& newAvatar = newSortData.getAvatar(); + bool inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; + // Once we reach an avatar that's not in view, all avatars after it will also be out of view + if (!inView) { + break; } - sortedAvatars.pop(); + numAVatarsNotUpdated += (int)(newAvatar->hasNewJointData()); + ++it; } break; } - sortedAvatars.pop(); } if (_shouldRender) { @@ -359,21 +359,21 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { QReadLocker locker(&_hashLock); QVector::iterator avatarItr = _avatarsToFade.begin(); const render::ScenePointer& scene = qApp->getMain3DScene(); + render::Transaction transaction; while (avatarItr != _avatarsToFade.end()) { auto avatar = std::static_pointer_cast(*avatarItr); avatar->updateFadingStatus(scene); if (!avatar->isFading()) { // fading to zero is such a rare event we push a unique transaction for each if (avatar->isInScene()) { - render::Transaction transaction; avatar->removeFromScene(*avatarItr, scene, transaction); - scene->enqueueTransaction(transaction); } avatarItr = _avatarsToFade.erase(avatarItr); } else { ++avatarItr; } } + scene->enqueueTransaction(transaction); } AvatarSharedPointer AvatarManager::newSharedAvatar() { @@ -439,6 +439,11 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar avatar->die(); queuePhysicsChange(avatar); + // remove this avatar's entities from the tree now, if we wait (as we did previously) for this Avatar's destructor + // it might not fire until after we create a new instance for the same remote avatar, which creates a race + // on the creation of entities for that avatar instance and the deletion of entities for this instance + avatar->removeAvatarEntitiesFromTree(); + if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) { emit DependencyManager::get()->enteredIgnoreRadius(); } else if (removalReason == KillAvatarReason::AvatarDisconnected) { @@ -451,31 +456,37 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar } void AvatarManager::clearOtherAvatars() { - // Remove other avatars from the world but don't actually remove them from _avatarHash - // each will either be removed on timeout or will re-added to the world on receipt of update. - const render::ScenePointer& scene = qApp->getMain3DScene(); - render::Transaction transaction; - - QReadLocker locker(&_hashLock); - AvatarHash::iterator avatarIterator = _avatarHash.begin(); - while (avatarIterator != _avatarHash.end()) { - auto avatar = std::static_pointer_cast(avatarIterator.value()); - if (avatar != _myAvatar) { - handleRemovedAvatar(avatar); - avatarIterator = _avatarHash.erase(avatarIterator); - } else { - ++avatarIterator; - } - } - assert(scene); - scene->enqueueTransaction(transaction); _myAvatar->clearLookAtTargetAvatar(); + + // setup a vector of removed avatars outside the scope of the hash lock + std::vector removedAvatars; + + { + QWriteLocker locker(&_hashLock); + + removedAvatars.reserve(_avatarHash.size()); + + auto avatarIterator = _avatarHash.begin(); + while (avatarIterator != _avatarHash.end()) { + auto avatar = std::static_pointer_cast(avatarIterator.value()); + if (avatar != _myAvatar) { + removedAvatars.push_back(avatar); + avatarIterator = _avatarHash.erase(avatarIterator); + } else { + ++avatarIterator; + } + } + } + + for (auto& av : removedAvatars) { + handleRemovedAvatar(av); + } } void AvatarManager::deleteAllAvatars() { assert(_avatarsToChangeInPhysics.empty()); - QReadLocker locker(&_hashLock); + QWriteLocker locker(&_hashLock); AvatarHash::iterator avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { auto avatar = std::static_pointer_cast(avatarIterator.value()); @@ -583,8 +594,14 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic return result; } - glm::vec3 normDirection = glm::normalize(ray.direction); + // It's better to intersect the ray 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. + + std::vector sortedAvatars; auto avatarHashCopy = getHashCopy(); for (auto avatarData : avatarHashCopy) { auto avatar = std::static_pointer_cast(avatarData); @@ -593,52 +610,65 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic continue; } - float distance; - BoxFace face; - glm::vec3 surfaceNormal; - - SkeletonModelPointer avatarModel = avatar->getSkeletonModel(); - - // It's better to intersect the ray 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. - + float distance = FLT_MAX; +#if 0 // if we weren't picking against the capsule, we would want to pick against the avatarBounds... - // AABox avatarBounds = avatarModel->getRenderableMeshBound(); - // if (!avatarBounds.findRayIntersection(ray.origin, normDirection, distance, face, surfaceNormal)) { - // // ray doesn't intersect avatar's bounding-box - // continue; - // } - + SkeletonModelPointer avatarModel = avatar->getSkeletonModel(); + AABox avatarBounds = avatarModel->getRenderableMeshBound(); + if (avatarBounds.contains(ray.origin)) { + distance = 0.0f; + } else { + float boundDistance = FLT_MAX; + BoxFace face; + glm::vec3 surfaceNormal; + if (avatarBounds.findRayIntersection(ray.origin, ray.direction, boundDistance, face, surfaceNormal)) { + distance = boundDistance; + } + } +#else glm::vec3 start; glm::vec3 end; float radius; avatar->getCapsule(start, end, radius); - bool intersects = findRayCapsuleIntersection(ray.origin, normDirection, start, end, radius, distance); - if (!intersects) { - // ray doesn't intersect avatar's capsule - continue; + findRayCapsuleIntersection(ray.origin, ray.direction, start, end, radius, distance); +#endif + + if (distance < FLT_MAX) { + sortedAvatars.emplace_back(distance, avatar); + } + } + + if (sortedAvatars.size() > 1) { + static auto comparator = [](const SortedAvatar& left, const SortedAvatar& right) { return left.first < right.first; }; + std::sort(sortedAvatars.begin(), sortedAvatars.end(), comparator); + } + + for (auto it = sortedAvatars.begin(); it != sortedAvatars.end(); ++it) { + const SortedAvatar& sortedAvatar = *it; + // We can exit once avatarCapsuleDistance > bestDistance + if (sortedAvatar.first > result.distance) { + break; } + float distance = FLT_MAX; + BoxFace face; + glm::vec3 surfaceNormal; QVariantMap extraInfo; - intersects = avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, normDirection, - distance, face, surfaceNormal, extraInfo, true); - - if (intersects && (!result.intersects || distance < result.distance)) { - result.intersects = true; - result.avatarID = avatar->getID(); - result.distance = distance; - result.face = face; - result.surfaceNormal = surfaceNormal; - result.extraInfo = extraInfo; + SkeletonModelPointer avatarModel = sortedAvatar.second->getSkeletonModel(); + if (avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, ray.direction, distance, face, surfaceNormal, extraInfo, true)) { + if (distance < result.distance) { + result.intersects = true; + result.avatarID = sortedAvatar.second->getID(); + result.distance = distance; + result.face = face; + result.surfaceNormal = surfaceNormal; + result.extraInfo = extraInfo; + } } } if (result.intersects) { - result.intersection = ray.origin + normDirection * result.distance; + result.intersection = ray.origin + ray.direction * result.distance; } return result; @@ -657,6 +687,14 @@ ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector return result; } + // It's better to intersect the ray 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. + + std::vector sortedAvatars; auto avatarHashCopy = getHashCopy(); for (auto avatarData : avatarHashCopy) { auto avatar = std::static_pointer_cast(avatarData); @@ -665,47 +703,60 @@ ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector 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. - + float distance = FLT_MAX; +#if 0 // 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; - // } - + SkeletonModelPointer avatarModel = avatar->getSkeletonModel(); + AABox avatarBounds = avatarModel->getRenderableMeshBound(); + if (avatarBounds.contains(pick.origin)) { + distance = 0.0f; + } else { + float boundDistance = FLT_MAX; + BoxFace face; + glm::vec3 surfaceNormal; + if (avatarBounds.findParabolaIntersection(pick.origin, pick.velocity, pick.acceleration, boundDistance, face, surfaceNormal)) { + distance = boundDistance; + } + } +#else 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; + findParabolaCapsuleIntersection(pick.origin, pick.velocity, pick.acceleration, start, end, radius, avatar->getWorldOrientation(), distance); +#endif + + if (distance < FLT_MAX) { + sortedAvatars.emplace_back(distance, avatar); + } + } + + if (sortedAvatars.size() > 1) { + static auto comparator = [](const SortedAvatar& left, const SortedAvatar& right) { return left.first < right.first; }; + std::sort(sortedAvatars.begin(), sortedAvatars.end(), comparator); + } + + for (auto it = sortedAvatars.begin(); it != sortedAvatars.end(); ++it) { + const SortedAvatar& sortedAvatar = *it; + // We can exit once avatarCapsuleDistance > bestDistance + if (sortedAvatar.first > result.parabolicDistance) { + break; } + float parabolicDistance = FLT_MAX; + BoxFace face; + glm::vec3 surfaceNormal; 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; + SkeletonModelPointer avatarModel = sortedAvatar.second->getSkeletonModel(); + if (avatarModel->findParabolaIntersectionAgainstSubMeshes(pick.origin, pick.velocity, pick.acceleration, parabolicDistance, face, surfaceNormal, extraInfo, true)) { + if (parabolicDistance < result.parabolicDistance) { + result.intersects = true; + result.avatarID = sortedAvatar.second->getID(); + result.parabolicDistance = parabolicDistance; + result.face = face; + result.surfaceNormal = surfaceNormal; + result.extraInfo = extraInfo; + } } } @@ -768,13 +819,13 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV QString currentSessionUUID = avatar->getSessionUUID().toString(); if (specificAvatarIdentifiers.isEmpty() || specificAvatarIdentifiers.contains(currentSessionUUID)) { QJsonObject thisAvatarPalData; - + auto myAvatar = DependencyManager::get()->getMyAvatar(); if (currentSessionUUID == myAvatar->getSessionUUID().toString()) { currentSessionUUID = ""; } - + thisAvatarPalData.insert("sessionUUID", currentSessionUUID); thisAvatarPalData.insert("sessionDisplayName", avatar->getSessionDisplayName()); thisAvatarPalData.insert("audioLoudness", avatar->getAudioLoudness()); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 36df2f0aaf..9c4287728d 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -31,6 +31,8 @@ #include "MyAvatar.h" #include "OtherAvatar.h" +using SortedAvatar = std::pair>; + /**jsdoc * The AvatarManager API has properties and methods which manage Avatars within the same domain. * @@ -89,9 +91,11 @@ public: void updateOtherAvatars(float deltaTime); void sendIdentityRequest(const QUuid& avatarID) const; + void setMyAvatarDataPacketsPaused(bool puase); + void postUpdate(float deltaTime, const render::ScenePointer& scene); - void clearOtherAvatars(); + void clearOtherAvatars() override; void deleteAllAvatars(); void getObjectsToRemoveFromPhysics(VectorOfMotionStates& motionStates); @@ -200,7 +204,12 @@ private: void simulateAvatarFades(float deltaTime); AvatarSharedPointer newSharedAvatar() override; - void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; + + // called only from the AvatarHashMap thread - cannot be called while this thread holds the + // hash lock, since handleRemovedAvatar needs a write lock on the entity tree and the entity tree + // frequently grabs a read lock on the hash to get a given avatar by ID + void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, + KillAvatarReason removalReason = KillAvatarReason::NoReason) override; QVector _avatarsToFade; @@ -217,6 +226,7 @@ private: int _numAvatarsNotUpdated { 0 }; float _avatarSimulationTime { 0.0f }; bool _shouldRender { true }; + bool _myAvatarDataPacketsPaused { false }; mutable int _identityRequestsSent { 0 }; mutable std::mutex _spaceLock; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 923d071cc0..df7ec93b6a 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -91,7 +91,7 @@ const float MIN_SCALE_CHANGED_DELTA = 0.001f; const int MODE_READINGS_RING_BUFFER_SIZE = 500; const float CENTIMETERS_PER_METER = 100.0f; -//#define DEBUG_DRAW_HMD_MOVING_AVERAGE +const QString AVATAR_SETTINGS_GROUP_NAME { "Avatar" }; MyAvatar::MyAvatar(QThread* thread) : Avatar(thread), @@ -115,11 +115,27 @@ MyAvatar::MyAvatar(QThread* thread) : _recentModeReadings(MODE_READINGS_RING_BUFFER_SIZE), _bodySensorMatrix(), _goToPending(false), + _goToSafe(true), _goToPosition(), _goToOrientation(), _prevShouldDrawHead(true), _audioListenerMode(FROM_HEAD), - _hmdAtRestDetector(glm::vec3(0), glm::quat()) + _hmdAtRestDetector(glm::vec3(0), glm::quat()), + _dominantHandSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "dominantHand", DOMINANT_RIGHT_HAND), + _headPitchSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "", 0.0f), + _scaleSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "scale", _targetScale), + _yawSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "yawSpeed", _yawSpeed), + _pitchSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "pitchSpeed", _pitchSpeed), + _fullAvatarURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "fullAvatarURL", + AvatarData::defaultFullAvatarModelUrl()), + _fullAvatarModelNameSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "fullAvatarModelName", _fullAvatarModelName), + _animGraphURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "animGraphURL", QUrl("")), + _displayNameSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "displayName", ""), + _collisionSoundURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "collisionSoundURL", QUrl(_collisionSoundURL)), + _useSnapTurnSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "useSnapTurn", _useSnapTurn), + _userHeightSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userHeight", DEFAULT_AVATAR_HEIGHT), + _flyingHMDSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "flyingHMD", _flyingPrefHMD), + _avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", _flyingPrefHMD) { _clientTraitsHandler = std::unique_ptr(new ClientTraitsHandler(this)); @@ -150,7 +166,8 @@ MyAvatar::MyAvatar(QThread* thread) : }); connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); - + connect(&_skeletonModel->getRig(), &Rig::onLoadComplete, this, &MyAvatar::updateCollisionCapsuleCache); + connect(this, &MyAvatar::sensorToWorldScaleChanged, this, &MyAvatar::updateCollisionCapsuleCache); using namespace recording; _skeletonModel->flagAsCauterized(); @@ -256,6 +273,7 @@ MyAvatar::MyAvatar(QThread* thread) : }); connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete())); + _characterController.setDensity(_density); } @@ -447,9 +465,27 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { void MyAvatar::update(float deltaTime) { // update moving average of HMD facing in xz plane. const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength(); + const float PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH = 0.0f; // 100 percent shoulders float tau = deltaTime / HMD_FACING_TIMESCALE; - _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau); + setHipToHandController(computeHandAzimuth()); + + // put the average hand azimuth into sensor space. + // then mix it with head facing direction to determine rotation recenter + if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid()) { + glm::vec3 handHipAzimuthWorldSpace = transformVectorFast(getTransform().getMatrix(), glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y)); + glm::mat4 sensorToWorldMat = getSensorToWorldMatrix(); + glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat); + glm::vec3 handHipAzimuthSensorSpace = transformVectorFast(worldToSensorMat, handHipAzimuthWorldSpace); + glm::vec2 normedHandHipAzimuthSensorSpace(0.0f, 1.0f); + if (glm::length(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z)) > 0.0f) { + normedHandHipAzimuthSensorSpace = glm::normalize(glm::vec2(handHipAzimuthSensorSpace.x, handHipAzimuthSensorSpace.z)); + } + glm::vec2 headFacingPlusHandHipAzimuthMix = lerp(normedHandHipAzimuthSensorSpace, _headControllerFacing, PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH); + _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, headFacingPlusHandHipAzimuthMix, tau); + } else { + _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau); + } if (_smoothOrientationTimer < SMOOTH_TIME_ORIENTATION) { _rotationChanged = usecTimestampNow(); @@ -462,19 +498,23 @@ void MyAvatar::update(float deltaTime) { setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD))); setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); -#ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE - auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); - glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation()); - glm::vec3 worldFacingAverage = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y)); - glm::vec3 worldFacing = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y)); - DebugDraw::getInstance().drawRay(worldHeadPos, worldHeadPos + worldFacing, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f)); - DebugDraw::getInstance().drawRay(worldHeadPos, worldHeadPos + worldFacingAverage, glm::vec4(0.0f, 0.0f, 1.0f, 1.0f)); -#endif + if (_drawAverageFacingEnabled) { + auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); + glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation()); + glm::vec3 worldFacingAverage = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y)); + glm::vec3 worldFacing = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y)); + DebugDraw::getInstance().drawRay(worldHeadPos, worldHeadPos + worldFacing, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f)); + DebugDraw::getInstance().drawRay(worldHeadPos, worldHeadPos + worldFacingAverage, glm::vec4(0.0f, 0.0f, 1.0f, 1.0f)); + + // draw hand azimuth vector + glm::vec3 handAzimuthMidpoint = transformPoint(getTransform().getMatrix(), glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y)); + DebugDraw::getInstance().drawRay(getWorldPosition(), handAzimuthMidpoint, glm::vec4(0.0f, 1.0f, 1.0f, 1.0f)); + } if (_goToPending) { setWorldPosition(_goToPosition); setWorldOrientation(_goToOrientation); - _headControllerFacingMovingAverage = _headControllerFacing; // reset moving average + _headControllerFacingMovingAverage = _headControllerFacing; // reset moving average _goToPending = false; // updateFromHMDSensorMatrix (called from paintGL) expects that the sensorToWorldMatrix is updated for any position changes // that happen between render and Application::update (which calls updateSensorToWorldMatrix to do so). @@ -489,13 +529,19 @@ void MyAvatar::update(float deltaTime) { if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { // When needed and ready, arrange to check and fix. _physicsSafetyPending = false; - safeLanding(_goToPosition); // no-op if already safe + if (_goToSafe) { + safeLanding(_goToPosition); // no-op if already safe + } } Head* head = getHead(); head->relax(deltaTime); updateFromTrackers(deltaTime); + if (getIsInWalkingState() && glm::length(getControllerPoseInAvatarFrame(controller::Action::HEAD).getVelocity()) < DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { + setIsInWalkingState(false); + } + // Get audio loudness data from audio input device // Also get the AudioClient so we can update the avatar bounding box data // on the AudioClient side. @@ -796,6 +842,47 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { } } +// Find the vector halfway between the hip to hand azimuth vectors +// This midpoint hand azimuth is in Avatar space +glm::vec2 MyAvatar::computeHandAzimuth() const { + controller::Pose leftHandPoseAvatarSpace = getLeftHandPose(); + controller::Pose rightHandPoseAvatarSpace = getRightHandPose(); + controller::Pose headPoseAvatarSpace = getControllerPoseInAvatarFrame(controller::Action::HEAD); + const float HALFWAY = 0.50f; + glm::vec2 latestHipToHandController = _hipToHandController; + + if (leftHandPoseAvatarSpace.isValid() && rightHandPoseAvatarSpace.isValid() && headPoseAvatarSpace.isValid()) { + // we need the old azimuth reading to prevent flipping the facing direction 180 + // in the case where the hands go from being slightly less than 180 apart to slightly more than 180 apart. + glm::vec2 oldAzimuthReading = _hipToHandController; + if ((glm::length(glm::vec2(rightHandPoseAvatarSpace.translation.x, rightHandPoseAvatarSpace.translation.z)) > 0.0f) && (glm::length(glm::vec2(leftHandPoseAvatarSpace.translation.x, leftHandPoseAvatarSpace.translation.z)) > 0.0f)) { + latestHipToHandController = lerp(glm::normalize(glm::vec2(rightHandPoseAvatarSpace.translation.x, rightHandPoseAvatarSpace.translation.z)), glm::normalize(glm::vec2(leftHandPoseAvatarSpace.translation.x, leftHandPoseAvatarSpace.translation.z)), HALFWAY); + } else { + latestHipToHandController = glm::vec2(0.0f, -1.0f); + } + + glm::vec3 headLookAtAvatarSpace = transformVectorFast(headPoseAvatarSpace.getMatrix(), glm::vec3(0.0f, 0.0f, 1.0f)); + glm::vec2 headAzimuthAvatarSpace = glm::vec2(headLookAtAvatarSpace.x, headLookAtAvatarSpace.z); + if (glm::length(headAzimuthAvatarSpace) > 0.0f) { + headAzimuthAvatarSpace = glm::normalize(headAzimuthAvatarSpace); + } else { + headAzimuthAvatarSpace = -latestHipToHandController; + } + + // check the angular distance from forward and back + float cosForwardAngle = glm::dot(latestHipToHandController, oldAzimuthReading); + float cosHeadShoulder = glm::dot(-latestHipToHandController, headAzimuthAvatarSpace); + // if we are now closer to the 180 flip of the previous chest forward + // then we negate our computed latestHipToHandController to keep the chest from flipping. + // also check the head to shoulder azimuth difference if we negate. + // don't negate the chest azimuth if this is greater than 100 degrees. + if ((cosForwardAngle < 0.0f) && !(cosHeadShoulder < -0.2f)) { + latestHipToHandController = -latestHipToHandController; + } + } + return latestHipToHandController; +} + void MyAvatar::updateJointFromController(controller::Action poseKey, ThreadSafeValueCache& matrixCache) { assert(QThread::currentThread() == thread()); auto userInputMapper = DependencyManager::get(); @@ -1065,88 +1152,80 @@ void MyAvatar::restoreRoleAnimation(const QString& role) { } void MyAvatar::saveAvatarUrl() { - Settings settings; - settings.beginGroup("Avatar"); - if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid() ) { - settings.setValue("fullAvatarURL", - _fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ? - "" : - _fullAvatarURLFromPreferences.toString()); + if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid()) { + _fullAvatarURLSetting.set(_fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ? + "" : + _fullAvatarURLFromPreferences.toString()); + } +} + +void MyAvatar::resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex) { + // The original Settings interface saved avatar-entity array data like this: + // Avatar/avatarEntityData/size: 5 + // Avatar/avatarEntityData/1/id: ... + // Avatar/avatarEntityData/1/properties: ... + // ... + // Avatar/avatarEntityData/5/id: ... + // Avatar/avatarEntityData/5/properties: ... + // + // Create Setting::Handles to mimic this. + + while (_avatarEntityIDSettings.size() <= avatarEntityIndex) { + Setting::Handle idHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" + << QString::number(avatarEntityIndex + 1) << "id", QUuid()); + _avatarEntityIDSettings.push_back(idHandle); + Setting::Handle dataHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" + << QString::number(avatarEntityIndex + 1) << "properties", QByteArray()); + _avatarEntityDataSettings.push_back(dataHandle); } - settings.endGroup(); } void MyAvatar::saveData() { - Settings settings; - settings.beginGroup("Avatar"); - - settings.setValue("dominantHand", _dominantHand); - settings.setValue("headPitch", getHead()->getBasePitch()); - - settings.setValue("scale", _targetScale); - - settings.setValue("yawSpeed", _yawSpeed); - settings.setValue("pitchSpeed", _pitchSpeed); + _dominantHandSetting.set(_dominantHand); + _headPitchSetting.set(getHead()->getBasePitch()); + _scaleSetting.set(_targetScale); + _yawSpeedSetting.set(_yawSpeed); + _pitchSpeedSetting.set(_pitchSpeed); // only save the fullAvatarURL if it has not been overwritten on command line // (so the overrideURL is not valid), or it was overridden _and_ we specified // --replaceAvatarURL (so _saveAvatarOverrideUrl is true) if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid() ) { - settings.setValue("fullAvatarURL", - _fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ? - "" : - _fullAvatarURLFromPreferences.toString()); + _fullAvatarURLSetting.set(_fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ? + "" : + _fullAvatarURLFromPreferences.toString()); } - settings.setValue("fullAvatarModelName", _fullAvatarModelName); - + _fullAvatarModelNameSetting.set(_fullAvatarModelName); QUrl animGraphUrl = _prefOverrideAnimGraphUrl.get(); - settings.setValue("animGraphURL", animGraphUrl); + _animGraphURLSetting.set(animGraphUrl); + _displayNameSetting.set(_displayName); + _collisionSoundURLSetting.set(_collisionSoundURL); + _useSnapTurnSetting.set(_useSnapTurn); + _userHeightSetting.set(getUserHeight()); + _flyingHMDSetting.set(getFlyingHMDPref()); - settings.beginWriteArray("attachmentData"); - for (int i = 0; i < _attachmentData.size(); i++) { - settings.setArrayIndex(i); - const AttachmentData& attachment = _attachmentData.at(i); - settings.setValue("modelURL", attachment.modelURL); - settings.setValue("jointName", attachment.jointName); - settings.setValue("translation_x", attachment.translation.x); - settings.setValue("translation_y", attachment.translation.y); - settings.setValue("translation_z", attachment.translation.z); - glm::vec3 eulers = safeEulerAngles(attachment.rotation); - settings.setValue("rotation_x", eulers.x); - settings.setValue("rotation_y", eulers.y); - settings.setValue("rotation_z", eulers.z); - settings.setValue("scale", attachment.scale); - settings.setValue("isSoft", attachment.isSoft); - } - settings.endArray(); - - settings.remove("avatarEntityData"); - settings.beginWriteArray("avatarEntityData"); - int avatarEntityIndex = 0; auto hmdInterface = DependencyManager::get(); _avatarEntitiesLock.withReadLock([&] { - for (auto entityID : _avatarEntityData.keys()) { - if (hmdInterface->getCurrentTabletFrameID() == entityID) { - // don't persist the tablet between domains / sessions - continue; - } + QList avatarEntityIDs = _avatarEntityData.keys(); + unsigned int avatarEntityCount = avatarEntityIDs.size(); + unsigned int previousAvatarEntityCount = _avatarEntityCountSetting.get(0); + resizeAvatarEntitySettingHandles(std::max(avatarEntityCount, previousAvatarEntityCount)); + _avatarEntityCountSetting.set(avatarEntityCount); - settings.setArrayIndex(avatarEntityIndex); - settings.setValue("id", entityID); - settings.setValue("properties", _avatarEntityData.value(entityID)); + unsigned int avatarEntityIndex = 0; + for (auto entityID : avatarEntityIDs) { + _avatarEntityIDSettings[avatarEntityIndex].set(entityID); + _avatarEntityDataSettings[avatarEntityIndex].set(_avatarEntityData.value(entityID)); avatarEntityIndex++; } + + // clean up any left-over (due to the list shrinking) slots + for (; avatarEntityIndex < previousAvatarEntityCount; avatarEntityIndex++) { + _avatarEntityIDSettings[avatarEntityIndex].remove(); + _avatarEntityDataSettings[avatarEntityIndex].remove(); + } }); - settings.endArray(); - - settings.setValue("displayName", _displayName); - settings.setValue("collisionSoundURL", _collisionSoundURL); - settings.setValue("useSnapTurn", _useSnapTurn); - settings.setValue("userHeight", getUserHeight()); - settings.setValue("flyingHMD", getFlyingHMDPref()); - - settings.endGroup(); } float loadSetting(Settings& settings, const QString& name, float defaultValue) { @@ -1244,66 +1323,36 @@ void MyAvatar::setEnableInverseKinematics(bool isEnabled) { } void MyAvatar::loadData() { - Settings settings; - settings.beginGroup("Avatar"); + getHead()->setBasePitch(_headPitchSetting.get()); - getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f)); + _yawSpeed = _yawSpeedSetting.get(_yawSpeed); + _pitchSpeed = _pitchSpeedSetting.get(_pitchSpeed); - _yawSpeed = loadSetting(settings, "yawSpeed", _yawSpeed); - _pitchSpeed = loadSetting(settings, "pitchSpeed", _pitchSpeed); - - _prefOverrideAnimGraphUrl.set(QUrl(settings.value("animGraphURL", "").toString())); - _fullAvatarURLFromPreferences = settings.value("fullAvatarURL", AvatarData::defaultFullAvatarModelUrl()).toUrl(); - _fullAvatarModelName = settings.value("fullAvatarModelName", DEFAULT_FULL_AVATAR_MODEL_NAME).toString(); + _prefOverrideAnimGraphUrl.set(_prefOverrideAnimGraphUrl.get().toString()); + _fullAvatarURLFromPreferences = _fullAvatarURLSetting.get(QUrl(AvatarData::defaultFullAvatarModelUrl())); + _fullAvatarModelName = _fullAvatarModelNameSetting.get(DEFAULT_FULL_AVATAR_MODEL_NAME).toString(); useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); - int attachmentCount = settings.beginReadArray("attachmentData"); - for (int i = 0; i < attachmentCount; i++) { - settings.setArrayIndex(i); - AttachmentData attachment; - attachment.modelURL = settings.value("modelURL").toUrl(); - attachment.jointName = settings.value("jointName").toString(); - attachment.translation.x = loadSetting(settings, "translation_x", 0.0f); - attachment.translation.y = loadSetting(settings, "translation_y", 0.0f); - attachment.translation.z = loadSetting(settings, "translation_z", 0.0f); - glm::vec3 eulers; - eulers.x = loadSetting(settings, "rotation_x", 0.0f); - eulers.y = loadSetting(settings, "rotation_y", 0.0f); - eulers.z = loadSetting(settings, "rotation_z", 0.0f); - attachment.rotation = glm::quat(eulers); - attachment.scale = loadSetting(settings, "scale", 1.0f); - attachment.isSoft = settings.value("isSoft").toBool(); - // old attachments are stored and loaded/converted later when rig is ready - _oldAttachmentData.append(attachment); - } - settings.endArray(); - - int avatarEntityCount = settings.beginReadArray("avatarEntityData"); + int avatarEntityCount = _avatarEntityCountSetting.get(0); for (int i = 0; i < avatarEntityCount; i++) { - settings.setArrayIndex(i); - QUuid entityID = settings.value("id").toUuid(); + resizeAvatarEntitySettingHandles(i); // QUuid entityID = QUuid::createUuid(); // generate a new ID - QByteArray properties = settings.value("properties").toByteArray(); + QUuid entityID = _avatarEntityIDSettings[i].get(QUuid()); + QByteArray properties = _avatarEntityDataSettings[i].get(); updateAvatarEntity(entityID, properties); } - settings.endArray(); - if (avatarEntityCount == 0) { - // HACK: manually remove empty 'avatarEntityData' else legacy data may persist in settings file - settings.remove("avatarEntityData"); - } // Flying preferences must be loaded before calling setFlyingEnabled() Setting::Handle firstRunVal { Settings::firstRun, true }; - setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool()); + setFlyingHMDPref(firstRunVal.get() ? false : _flyingHMDSetting.get()); setFlyingEnabled(getFlyingEnabled()); - setDisplayName(settings.value("displayName").toString()); - setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); - setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); - setDominantHand(settings.value("dominantHand", _dominantHand).toString().toLower()); - setUserHeight(settings.value("userHeight", DEFAULT_AVATAR_HEIGHT).toDouble()); - settings.endGroup(); + setDisplayName(_displayNameSetting.get()); + setCollisionSoundURL(_collisionSoundURLSetting.get(QUrl(DEFAULT_AVATAR_COLLISION_SOUND_URL)).toString()); + setSnapTurn(_useSnapTurnSetting.get()); + setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower()); + setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT)); setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible)); _follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing)); @@ -1372,6 +1421,7 @@ AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString& return attachment; } + int MyAvatar::parseDataFromBuffer(const QByteArray& buffer) { qCDebug(interfaceapp) << "Error: ignoring update packet for MyAvatar" << " packetLength = " << buffer.size(); @@ -1703,18 +1753,50 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { emit skeletonChanged(); } -void MyAvatar::removeAvatarEntities(const std::function& condition) { +bool isWearableEntity(const EntityItemPointer& entity) { + return entity->isVisible() + && (entity->getParentJointIndex() != INVALID_JOINT_INDEX + || (entity->getType() == EntityTypes::Model && (std::static_pointer_cast(entity))->getRelayParentJoints())) + && (entity->getParentID() == DependencyManager::get()->getSessionUUID() + || entity->getParentID() == AVATAR_SELF_ID); +} + +void MyAvatar::clearAvatarEntities() { auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - if (entityTree) { - entityTree->withWriteLock([&] { - AvatarEntityMap avatarEntities = getAvatarEntityData(); - for (auto entityID : avatarEntities.keys()) { - if (!condition || condition(entityID)) { - entityTree->deleteEntity(entityID, true, true); - } - } + + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + entityTree->withWriteLock([&entityID, &entityTree] { + // remove this entity first from the entity tree + entityTree->deleteEntity(entityID, true, true); }); + + // remove the avatar entity from our internal list + // (but indicate it doesn't need to be pulled from the tree) + clearAvatarEntity(entityID, false); + } +} + +void MyAvatar::removeWearableAvatarEntities() { + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + + if (entityTree) { + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + auto entity = entityTree->findEntityByID(entityID); + if (entity && isWearableEntity(entity)) { + entityTree->withWriteLock([&entityID, &entityTree] { + // remove this entity first from the entity tree + entityTree->deleteEntity(entityID, true, true); + }); + + // remove the avatar entity from our internal list + // (but indicate it doesn't need to be pulled from the tree) + clearAvatarEntity(entityID, false); + } + } } } @@ -2116,7 +2198,7 @@ void MyAvatar::setAttachmentData(const QVector& attachmentData) } // clear any existing avatar entities - setAvatarEntityData(AvatarEntityMap()); + clearAvatarEntities(); for (auto& properties : newEntitiesProperties) { DependencyManager::get()->addEntity(properties, true); @@ -2212,7 +2294,11 @@ void MyAvatar::initHeadBones() { neckJointIndex = _skeletonModel->getFBXGeometry().neckJointIndex; } if (neckJointIndex == -1) { - return; + neckJointIndex = (_skeletonModel->getFBXGeometry().headJointIndex - 1); + if (neckJointIndex < 0) { + // return if the head is not even there. can't cauterize!! + return; + } } _headBoneSet.clear(); std::queue q; @@ -2793,10 +2879,7 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings } // Set avatar current scale - Settings settings; - settings.beginGroup("Avatar"); - _targetScale = loadSetting(settings, "scale", 1.0f); - + _targetScale = _scaleSetting.get(); // clamp the desired _targetScale by the domain limits NOW, don't try to gracefully animate. Because // this might cause our avatar to become embedded in the terrain. _targetScale = getDomainLimitedScale(); @@ -2808,7 +2891,6 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings setModelScale(_targetScale); rebuildCollisionShape(); - settings.endGroup(); _haveReceivedHeightLimitsFromDomain = true; } @@ -2819,10 +2901,7 @@ void MyAvatar::leaveDomain() { } void MyAvatar::saveAvatarScale() { - Settings settings; - settings.beginGroup("Avatar"); - settings.setValue("scale", _targetScale); - settings.endGroup(); + _scaleSetting.set(_targetScale); } void MyAvatar::clearScaleRestriction() { @@ -2910,7 +2989,7 @@ void MyAvatar::goToFeetLocation(const glm::vec3& newPosition, void MyAvatar::goToLocation(const glm::vec3& newPosition, bool hasOrientation, const glm::quat& newOrientation, - bool shouldFaceLocation) { + bool shouldFaceLocation, bool withSafeLanding) { // Most cases of going to a place or user go through this now. Some possible improvements to think about in the future: // - It would be nice if this used the same teleport steps and smoothing as in the teleport.js script, as long as it @@ -2930,6 +3009,7 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, _goToPending = true; _goToPosition = newPosition; + _goToSafe = withSafeLanding; _goToOrientation = getWorldOrientation(); if (hasOrientation) { qCDebug(interfaceapp).nospace() << "MyAvatar goToLocation - new orientation is " @@ -3202,6 +3282,22 @@ bool MyAvatar::getCollisionsEnabled() { return _characterController.computeCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS; } +void MyAvatar::updateCollisionCapsuleCache() { + glm::vec3 start, end; + float radius; + getCapsule(start, end, radius); + QVariantMap capsule; + capsule["start"] = vec3toVariant(start); + capsule["end"] = vec3toVariant(end); + capsule["radius"] = QVariant(radius); + _collisionCapsuleCache.set(capsule); +} + +// thread safe +QVariantMap MyAvatar::getCollisionCapsule() const { + return _collisionCapsuleCache.get(); +} + void MyAvatar::setCharacterControllerEnabled(bool enabled) { qCDebug(interfaceapp) << "MyAvatar.characterControllerEnabled is deprecated. Use MyAvatar.collisionsEnabled instead."; setCollisionsEnabled(enabled); @@ -3321,6 +3417,24 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { return createMatFromQuatAndPos(headOrientationYawOnly, bodyPos); } +glm::mat4 MyAvatar::getSpine2RotationRigSpace() const { + + // static const glm::quat RIG_CHANGE_OF_BASIS = Quaternions::Y_180; + // RIG_CHANGE_OF_BASIS * AVATAR_TO_RIG_ROTATION * inverse(RIG_CHANGE_OF_BASIS) = Quaternions::Y_180; //avatar Space; + const glm::quat AVATAR_TO_RIG_ROTATION = Quaternions::Y_180; + glm::vec3 hipToHandRigSpace = AVATAR_TO_RIG_ROTATION * glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y); + + glm::vec3 u, v, w; + if (glm::length(hipToHandRigSpace) > 0.0f) { + hipToHandRigSpace = glm::normalize(hipToHandRigSpace); + } else { + hipToHandRigSpace = glm::vec3(0.0f, 0.0f, 1.0f); + } + generateBasisVectors(glm::vec3(0.0f,1.0f,0.0f), hipToHandRigSpace, u, v, w); + glm::mat4 spine2RigSpace(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f)); + return spine2RigSpace; +} + // ease in function for dampening cg movement static float slope(float num) { const float CURVE_CONSTANT = 1.0f; @@ -3563,10 +3677,10 @@ static bool headAngularVelocityBelowThreshold(const controller::Pose& head) { return isBelowThreshold; } -static bool isWithinThresholdHeightMode(const controller::Pose& head,const float& newMode) { +static bool isWithinThresholdHeightMode(const controller::Pose& head, const float& newMode, const float& scale) { bool isWithinThreshold = true; if (head.isValid()) { - isWithinThreshold = (head.getTranslation().y - newMode) > DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD; + isWithinThreshold = (head.getTranslation().y - newMode) > (DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD * scale); } return isWithinThreshold; } @@ -3687,6 +3801,10 @@ float MyAvatar::getUserEyeHeight() const { return userHeight - userHeight * ratio; } +bool MyAvatar::getIsInWalkingState() const { + return _isInWalkingState; +} + float MyAvatar::getWalkSpeed() const { return _walkSpeed.get() * _walkSpeedScalar; } @@ -3703,6 +3821,10 @@ void MyAvatar::setSprintMode(bool sprint) { _walkSpeedScalar = sprint ? _sprintSpeed.get() : AVATAR_WALK_SPEED_SCALAR; } +void MyAvatar::setIsInWalkingState(bool isWalking) { + _isInWalkingState = isWalking; +} + void MyAvatar::setWalkSpeed(float value) { _walkSpeed.set(value); } @@ -3797,7 +3919,6 @@ void MyAvatar::lateUpdatePalms() { Avatar::updatePalms(); } - static const float FOLLOW_TIME = 0.5f; MyAvatar::FollowHelper::FollowHelper() { @@ -3889,24 +4010,36 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); bool stepDetected = false; - if (!withinBaseOfSupport(currentHeadPose) && + float myScale = myAvatar.getAvatarScale(); + + if (myAvatar.getIsInWalkingState()) { + stepDetected = true; + } else { + if (!withinBaseOfSupport(currentHeadPose) && headAngularVelocityBelowThreshold(currentHeadPose) && - isWithinThresholdHeightMode(currentHeadPose, myAvatar.getCurrentStandingHeight()) && + isWithinThresholdHeightMode(currentHeadPose, myAvatar.getCurrentStandingHeight(), myScale) && handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) && handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) && headVelocityGreaterThanThreshold(currentHeadPose) && isHeadLevel(currentHeadPose, myAvatar.getAverageHeadRotation())) { - // a step is detected - stepDetected = true; - } else { - glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); - glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); - glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); - float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); - if (!isActive(Horizontal) && - (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + DEFAULT_AVATAR_SPINE_STRETCH_LIMIT))) { - myAvatar.setResetMode(true); + // a step is detected stepDetected = true; + if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { + myAvatar.setIsInWalkingState(true); + } + } else { + glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); + glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); + glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); + float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); + if (!isActive(Horizontal) && + (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { + myAvatar.setResetMode(true); + stepDetected = true; + if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { + myAvatar.setIsInWalkingState(true); + } + } } } return stepDetected; @@ -3928,25 +4061,32 @@ 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); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } if (myAvatar.getCenterOfGravityModelEnabled()) { if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { activate(Horizontal); + if (myAvatar.getEnableStepResetRotation()) { + activate(Rotation); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); + } } } else { if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Horizontal); + if (myAvatar.getEnableStepResetRotation()) { + activate(Rotation); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); + } } } - if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Vertical); } } else { if (!isActive(Rotation) && getForceActivateRotation()) { activate(Rotation); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar._headControllerFacing); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); setForceActivateRotation(false); } if (!isActive(Horizontal) && getForceActivateHorizontal()) { @@ -4191,7 +4331,8 @@ glm::mat4 MyAvatar::getCenterEyeCalibrationMat() const { auto centerEyeRot = Quaternions::Y_180; return createMatFromQuatAndPos(centerEyeRot, centerEyePos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_ROT, DEFAULT_AVATAR_MIDDLE_EYE_POS / getSensorToWorldScale()); + glm::mat4 headMat = getHeadCalibrationMat(); + return createMatFromQuatAndPos(DEFAULT_AVATAR_MIDDLE_EYE_ROT, extractTranslation(headMat) + DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); } } @@ -4201,9 +4342,10 @@ glm::mat4 MyAvatar::getHeadCalibrationMat() const { if (headIndex >= 0) { auto headPos = getAbsoluteDefaultJointTranslationInObjectFrame(headIndex); auto headRot = getAbsoluteDefaultJointRotationInObjectFrame(headIndex); + return createMatFromQuatAndPos(headRot, headPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_HEAD_ROT, DEFAULT_AVATAR_HEAD_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_HEAD_ROT, DEFAULT_AVATAR_HEAD_POS); } } @@ -4215,7 +4357,7 @@ glm::mat4 MyAvatar::getSpine2CalibrationMat() const { auto spine2Rot = getAbsoluteDefaultJointRotationInObjectFrame(spine2Index); return createMatFromQuatAndPos(spine2Rot, spine2Pos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_SPINE2_ROT, DEFAULT_AVATAR_SPINE2_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_SPINE2_ROT, DEFAULT_AVATAR_SPINE2_POS); } } @@ -4227,7 +4369,7 @@ glm::mat4 MyAvatar::getHipsCalibrationMat() const { auto hipsRot = getAbsoluteDefaultJointRotationInObjectFrame(hipsIndex); return createMatFromQuatAndPos(hipsRot, hipsPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_HIPS_ROT, DEFAULT_AVATAR_HIPS_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_HIPS_ROT, DEFAULT_AVATAR_HIPS_POS); } } @@ -4239,7 +4381,7 @@ glm::mat4 MyAvatar::getLeftFootCalibrationMat() const { auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex); return createMatFromQuatAndPos(leftFootRot, leftFootPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_ROT, DEFAULT_AVATAR_LEFTFOOT_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_ROT, DEFAULT_AVATAR_LEFTFOOT_POS); } } @@ -4251,11 +4393,10 @@ glm::mat4 MyAvatar::getRightFootCalibrationMat() const { auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex); return createMatFromQuatAndPos(rightFootRot, rightFootPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_ROT, DEFAULT_AVATAR_RIGHTFOOT_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_ROT, DEFAULT_AVATAR_RIGHTFOOT_POS); } } - glm::mat4 MyAvatar::getRightArmCalibrationMat() const { int rightArmIndex = _skeletonModel->getRig().indexOfJoint("RightArm"); if (rightArmIndex >= 0) { @@ -4263,7 +4404,7 @@ glm::mat4 MyAvatar::getRightArmCalibrationMat() const { auto rightArmRot = getAbsoluteDefaultJointRotationInObjectFrame(rightArmIndex); return createMatFromQuatAndPos(rightArmRot, rightArmPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTARM_ROT, DEFAULT_AVATAR_RIGHTARM_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTARM_ROT, DEFAULT_AVATAR_RIGHTARM_POS); } } @@ -4274,7 +4415,7 @@ glm::mat4 MyAvatar::getLeftArmCalibrationMat() const { auto leftArmRot = getAbsoluteDefaultJointRotationInObjectFrame(leftArmIndex); return createMatFromQuatAndPos(leftArmRot, leftArmPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_LEFTARM_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_LEFTARM_POS); } } @@ -4285,7 +4426,7 @@ glm::mat4 MyAvatar::getRightHandCalibrationMat() const { auto rightHandRot = getAbsoluteDefaultJointRotationInObjectFrame(rightHandIndex); return createMatFromQuatAndPos(rightHandRot, rightHandPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTHAND_ROT, DEFAULT_AVATAR_RIGHTHAND_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTHAND_ROT, DEFAULT_AVATAR_RIGHTHAND_POS); } } @@ -4296,7 +4437,7 @@ glm::mat4 MyAvatar::getLeftHandCalibrationMat() const { auto leftHandRot = getAbsoluteDefaultJointRotationInObjectFrame(leftHandIndex); return createMatFromQuatAndPos(leftHandRot, leftHandPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTHAND_ROT, DEFAULT_AVATAR_LEFTHAND_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTHAND_ROT, DEFAULT_AVATAR_LEFTHAND_POS); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index ba6348cc22..1dc0b3cd40 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -201,6 +201,8 @@ class MyAvatar : public Avatar { Q_PROPERTY(bool hasAudioEnabledFaceMovement READ getHasAudioEnabledFaceMovement WRITE setHasAudioEnabledFaceMovement) Q_PROPERTY(float rotationRecenterFilterLength READ getRotationRecenterFilterLength WRITE setRotationRecenterFilterLength) Q_PROPERTY(float rotationThreshold READ getRotationThreshold WRITE setRotationThreshold) + Q_PROPERTY(bool enableStepResetRotation READ getEnableStepResetRotation WRITE setEnableStepResetRotation) + Q_PROPERTY(bool enableDrawAverageFacing READ getEnableDrawAverageFacing WRITE setEnableDrawAverageFacing) //TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity) Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition) @@ -318,6 +320,9 @@ public: // as it moves through the world. void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix); + // compute the hip to hand average azimuth. + glm::vec2 computeHandAzimuth() const; + // read the location of a hand controller and save the transform void updateJointFromController(controller::Action poseKey, ThreadSafeValueCache& matrixCache); @@ -545,6 +550,7 @@ public: float getHMDRollControlRate() const { return _hmdRollControlRate; } // get/set avatar data + void resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex); void saveData(); void loadData(); @@ -909,6 +915,10 @@ public: virtual void rebuildCollisionShape() override; + const glm::vec2& getHipToHandController() const { return _hipToHandController; } + void setHipToHandController(glm::vec2 currentHipToHandController) { _hipToHandController = currentHipToHandController; } + const glm::vec2& getHeadControllerFacing() const { return _headControllerFacing; } + void setHeadControllerFacing(glm::vec2 currentHeadControllerFacing) { _headControllerFacing = currentHeadControllerFacing; } const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; } void setHeadControllerFacingMovingAverage(glm::vec2 currentHeadControllerFacing) { _headControllerFacingMovingAverage = currentHeadControllerFacing; } float getCurrentStandingHeight() const { return _currentStandingHeight; } @@ -931,7 +941,8 @@ public: * @returns {object[]} */ Q_INVOKABLE QVariantList getAvatarEntitiesVariant(); - void removeAvatarEntities(const std::function& condition = {}); + void clearAvatarEntities(); + void removeWearableAvatarEntities(); /**jsdoc * @function MyAvatar.isFlying @@ -1007,6 +1018,12 @@ public: */ Q_INVOKABLE bool getCollisionsEnabled(); + /**jsdoc + * @function MyAvatar.getCollisionCapsule + * @returns {object} + */ + Q_INVOKABLE QVariantMap getCollisionCapsule() const; + /**jsdoc * @function MyAvatar.setCharacterControllerEnabled * @param {boolean} enabled @@ -1024,7 +1041,7 @@ public: virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; - // all calibration matrices are in absolute avatar space. + // all calibration matrices are in absolute sensor space. glm::mat4 getCenterEyeCalibrationMat() const; glm::mat4 getHeadCalibrationMat() const; glm::mat4 getSpine2CalibrationMat() const; @@ -1045,6 +1062,8 @@ public: // results are in sensor frame (-z forward) glm::mat4 deriveBodyFromHMDSensor() const; + glm::mat4 getSpine2RotationRigSpace() const; + glm::vec3 computeCounterBalance(); // derive avatar body position and orientation from using the current HMD Sensor location in relation to the previous @@ -1074,6 +1093,8 @@ public: const QUuid& getSelfID() const { return AVATAR_SELF_ID; } + void setIsInWalkingState(bool isWalking); + bool getIsInWalkingState() const; void setWalkSpeed(float value); float getWalkSpeed() const; void setWalkBackwardSpeed(float value); @@ -1166,11 +1187,12 @@ public slots: * @param {boolean} [hasOrientation=false] - Set to true to set the orientation of the avatar. * @param {Quat} [orientation=Quat.IDENTITY] - The new orientation for the avatar. * @param {boolean} [shouldFaceLocation=false] - Set to true to position the avatar a short distance away from + * @param {boolean} [withSafeLanding=true] - Set to false MyAvatar::safeLanding will not be called (used when teleporting). * the new position and orientate the avatar to face the position. */ void goToLocation(const glm::vec3& newPosition, bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(), - bool shouldFaceLocation = false); + bool shouldFaceLocation = false, bool withSafeLanding = true); /**jsdoc * @function MyAvatar.goToLocation * @param {object} properties @@ -1484,6 +1506,7 @@ signals: private slots: void leaveDomain(); + void updateCollisionCapsuleCache(); protected: virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override; @@ -1515,6 +1538,10 @@ private: float getRotationRecenterFilterLength() const { return _rotationRecenterFilterLength; } void setRotationThreshold(float angleRadians); float getRotationThreshold() const { return _rotationThreshold; } + void setEnableStepResetRotation(bool stepReset) { _stepResetRotationEnabled = stepReset; } + bool getEnableStepResetRotation() const { return _stepResetRotationEnabled; } + void setEnableDrawAverageFacing(bool drawAverage) { _drawAverageFacingEnabled = drawAverage; } + bool getEnableDrawAverageFacing() const { return _drawAverageFacingEnabled; } bool isMyAvatar() const override { return true; } virtual int parseDataFromBuffer(const QByteArray& buffer) override; virtual glm::vec3 getSkeletonPosition() const override; @@ -1637,6 +1664,8 @@ private: std::atomic _hasScriptedBlendShapes { false }; std::atomic _rotationRecenterFilterLength { 4.0f }; std::atomic _rotationThreshold { 0.5235f }; // 30 degrees in radians + std::atomic _stepResetRotationEnabled { true }; + std::atomic _drawAverageFacingEnabled { false }; // working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access glm::mat4 _sensorToWorldMatrix { glm::mat4() }; @@ -1650,6 +1679,8 @@ private: glm::vec2 _headControllerFacingMovingAverage { 0.0f, 0.0f }; // facing vector in xz plane (sensor space) glm::quat _averageHeadRotation { 0.0f, 0.0f, 0.0f, 1.0f }; + glm::vec2 _hipToHandController { 0.0f, -1.0f }; // spine2 facing vector in xz plane (avatar space) + float _currentStandingHeight { 0.0f }; bool _resetMode { true }; RingBufferHistory _recentModeReadings; @@ -1700,6 +1731,7 @@ private: bool _goToPending { false }; bool _physicsSafetyPending { false }; + bool _goToSafe { true }; glm::vec3 _goToPosition; glm::quat _goToOrientation; @@ -1768,12 +1800,30 @@ private: ThreadSafeValueCache _walkBackwardSpeed { DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED }; ThreadSafeValueCache _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; + bool _isInWalkingState { false }; // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; bool _haveReceivedHeightLimitsFromDomain { false }; int _disableHandTouchCount { 0 }; + + Setting::Handle _dominantHandSetting; + Setting::Handle _headPitchSetting; + Setting::Handle _scaleSetting; + Setting::Handle _yawSpeedSetting; + Setting::Handle _pitchSpeedSetting; + Setting::Handle _fullAvatarURLSetting; + Setting::Handle _fullAvatarModelNameSetting; + Setting::Handle _animGraphURLSetting; + Setting::Handle _displayNameSetting; + Setting::Handle _collisionSoundURLSetting; + Setting::Handle _useSnapTurnSetting; + Setting::Handle _userHeightSetting; + Setting::Handle _flyingHMDSetting; + Setting::Handle _avatarEntityCountSetting; + std::vector> _avatarEntityIDSettings; + std::vector> _avatarEntityDataSettings; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); @@ -1782,4 +1832,6 @@ void audioListenModeFromScriptValue(const QScriptValue& object, AudioListenerMod QScriptValue driveKeysToScriptValue(QScriptEngine* engine, const MyAvatar::DriveKeys& driveKeys); void driveKeysFromScriptValue(const QScriptValue& object, MyAvatar::DriveKeys& driveKeys); +bool isWearableEntity(const EntityItemPointer& entity); + #endif // hifi_MyAvatar_h diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 0fc5e7521e..3084542472 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -46,7 +46,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { } glm::mat4 hipsMat; - if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying) { + if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState())) { // then we use center of gravity model hipsMat = myAvatar->deriveBodyUsingCgModel(); } else { @@ -238,6 +238,35 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { params.primaryControllerPoses[Rig::PrimaryControllerType_Hips] = sensorToRigPose * hips; params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; + // set spine2 if we have hand controllers + if (myAvatar->getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && + myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && + !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { + + AnimPose currentSpine2Pose; + AnimPose currentHeadPose; + AnimPose currentHipsPose; + bool spine2Exists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Spine2"), currentSpine2Pose); + bool headExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Head"), currentHeadPose); + bool hipsExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Hips"), currentHipsPose); + if (spine2Exists && headExists && hipsExists) { + AnimPose rigSpaceYaw(myAvatar->getSpine2RotationRigSpace()); + glm::vec3 u, v, w; + glm::vec3 fwd = rigSpaceYaw.rot() * glm::vec3(0.0f, 0.0f, 1.0f); + glm::vec3 up = currentHeadPose.trans() - currentHipsPose.trans(); + if (glm::length(up) > 0.0f) { + up = glm::normalize(up); + } else { + up = glm::vec3(0.0f, 1.0f, 0.0f); + } + generateBasisVectors(up, fwd, u, v, w); + AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); + currentSpine2Pose.rot() = newSpinePose.rot(); + params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose; + params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; + } + } + } else { _prevHipsValid = false; } diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 29ad5aed91..29fa98fd1d 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -11,6 +11,25 @@ #include "AvatarMotionState.h" +static xColor getLoadingOrbColor(Avatar::LoadingStatus loadingStatus) { + + const xColor NO_MODEL_COLOR(0xe3, 0xe3, 0xe3); + const xColor LOAD_MODEL_COLOR(0xef, 0x93, 0xd1); + const xColor LOAD_SUCCESS_COLOR(0x1f, 0xc6, 0xa6); + const xColor LOAD_FAILURE_COLOR(0xc6, 0x21, 0x47); + switch (loadingStatus) { + case Avatar::LoadingStatus::NoModel: + return NO_MODEL_COLOR; + case Avatar::LoadingStatus::LoadModel: + return LOAD_MODEL_COLOR; + case Avatar::LoadingStatus::LoadSuccess: + return LOAD_SUCCESS_COLOR; + case Avatar::LoadingStatus::LoadFailure: + default: + return LOAD_FAILURE_COLOR; + } +} + OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { // give the pointer to our head to inherited _headData variable from AvatarData _headData = new Head(this); @@ -29,23 +48,26 @@ OtherAvatar::~OtherAvatar() { } void OtherAvatar::removeOrb() { - if (qApp->getOverlays().isAddedOverlay(_otherAvatarOrbMeshPlaceholderID)) { + if (!_otherAvatarOrbMeshPlaceholderID.isNull()) { qApp->getOverlays().deleteOverlay(_otherAvatarOrbMeshPlaceholderID); + _otherAvatarOrbMeshPlaceholderID = UNKNOWN_OVERLAY_ID; } } void OtherAvatar::updateOrbPosition() { if (_otherAvatarOrbMeshPlaceholder != nullptr) { _otherAvatarOrbMeshPlaceholder->setWorldPosition(getHead()->getPosition()); + if (_otherAvatarOrbMeshPlaceholderID.isNull()) { + _otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder); + } } } void OtherAvatar::createOrb() { - if (_otherAvatarOrbMeshPlaceholderID == UNKNOWN_OVERLAY_ID || - !qApp->getOverlays().isAddedOverlay(_otherAvatarOrbMeshPlaceholderID)) { + if (_otherAvatarOrbMeshPlaceholderID.isNull()) { _otherAvatarOrbMeshPlaceholder = std::make_shared(); _otherAvatarOrbMeshPlaceholder->setAlpha(1.0f); - _otherAvatarOrbMeshPlaceholder->setColor({ 0xFF, 0x00, 0xFF }); + _otherAvatarOrbMeshPlaceholder->setColor(getLoadingOrbColor(_loadingStatus)); _otherAvatarOrbMeshPlaceholder->setIsSolid(false); _otherAvatarOrbMeshPlaceholder->setPulseMin(0.5); _otherAvatarOrbMeshPlaceholder->setPulseMax(1.0); @@ -61,6 +83,13 @@ void OtherAvatar::createOrb() { } } +void OtherAvatar::indicateLoadingStatus(LoadingStatus loadingStatus) { + Avatar::indicateLoadingStatus(loadingStatus); + if (_otherAvatarOrbMeshPlaceholder) { + _otherAvatarOrbMeshPlaceholder->setColor(getLoadingOrbColor(_loadingStatus)); + } +} + void OtherAvatar::setSpaceIndex(int32_t index) { assert(_spaceIndex == -1); _spaceIndex = index; diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index ccfe42dbed..94b98f2747 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -28,6 +28,7 @@ public: virtual void instantiableAvatar() override { }; virtual void createOrb() override; + virtual void indicateLoadingStatus(LoadingStatus loadingStatus) override; void updateOrbPosition(); void removeOrb(); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 1c6600cf3f..06da18148f 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -40,11 +40,9 @@ QmlCommerce::QmlCommerce() { connect(ledger.data(), &Ledger::transferAssetToUsernameResult, this, &QmlCommerce::transferAssetToUsernameResult); connect(ledger.data(), &Ledger::availableUpdatesResult, this, &QmlCommerce::availableUpdatesResult); connect(ledger.data(), &Ledger::updateItemResult, this, &QmlCommerce::updateItemResult); - + auto accountManager = DependencyManager::get(); - connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { - setPassphrase(""); - }); + connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { setPassphrase(""); }); _appsPath = PathUtils::getAppDataPath() + "Apps/"; } @@ -105,7 +103,11 @@ void QmlCommerce::balance() { } } -void QmlCommerce::inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage) { +void QmlCommerce::inventory(const QString& editionFilter, + const QString& typeFilter, + const QString& titleFilter, + const int& page, + const int& perPage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); @@ -166,24 +168,30 @@ void QmlCommerce::certificateInfo(const QString& certificateId) { ledger->certificateInfo(certificateId); } -void QmlCommerce::transferAssetToNode(const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage) { +void QmlCommerce::transferAssetToNode(const QString& nodeID, + const QString& certificateID, + const int& amount, + const QString& optionalMessage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList keys = wallet->listPublicKeys(); if (keys.count() == 0) { - QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + QJsonObject result{ { "status", "fail" }, { "message", "Uninitialized Wallet." } }; return emit transferAssetToNodeResult(result); } QString key = keys[0]; ledger->transferAssetToNode(key, nodeID, certificateID, amount, optionalMessage); } -void QmlCommerce::transferAssetToUsername(const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage) { +void QmlCommerce::transferAssetToUsername(const QString& username, + const QString& certificateID, + const int& amount, + const QString& optionalMessage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList keys = wallet->listPublicKeys(); if (keys.count() == 0) { - QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + QJsonObject result{ { "status", "fail" }, { "message", "Uninitialized Wallet." } }; return emit transferAssetToUsernameResult(result); } QString key = keys[0]; @@ -194,10 +202,7 @@ void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& cert auto ledger = DependencyManager::get(); ledger->updateLocation(certificateID, DependencyManager::get()->getPlaceName(), true); qApp->replaceDomainContent(itemHref); - QJsonObject messageProperties = { - { "status", "SuccessfulRequestToReplaceContent" }, - { "content_set_url", itemHref } - }; + QJsonObject messageProperties = { { "status", "SuccessfulRequestToReplaceContent" }, { "content_set_url", itemHref } }; UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties); emit contentSetChanged(itemHref); @@ -214,10 +219,7 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) { QDir directory(_appsPath); QStringList apps = directory.entryList(QStringList("*.app.json")); - foreach(QString appFileName, apps) { - installedAppsFromMarketplace += appFileName; - installedAppsFromMarketplace += ","; - + foreach (QString appFileName, apps) { // If we were supplied a "justInstalledAppID" argument, that means we're entering this function // to get the new list of installed apps immediately after installing an app. // In that case, the app we installed may not yet have its associated script running - @@ -243,10 +245,12 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) { // delete the .app.json from the user's local disk. if (!runningScripts.contains(scriptURL)) { if (!appFile.remove()) { - qCWarning(commerce) - << "Couldn't delete local .app.json file (app's script isn't running). App filename is:" - << appFileName; + qCWarning(commerce) << "Couldn't delete local .app.json file (app's script isn't running). App filename is:" + << appFileName; } + } else { + installedAppsFromMarketplace += appFileName; + installedAppsFromMarketplace += ","; } } else { qCDebug(commerce) << "Couldn't open local .app.json file for reading."; @@ -317,7 +321,9 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) { // Read from the file to know what .js script to stop QFile appFile(_appsPath + "/" + appHref.fileName()); if (!appFile.open(QIODevice::ReadOnly)) { - qCDebug(commerce) << "Couldn't open local .app.json file for deletion. Cannot continue with app uninstallation. App filename is:" << appHref.fileName(); + qCDebug(commerce) + << "Couldn't open local .app.json file for deletion. Cannot continue with app uninstallation. App filename is:" + << appHref.fileName(); return false; } QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll()); @@ -325,13 +331,15 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) { QString scriptUrl = appFileJsonObject["scriptURL"].toString(); if (!DependencyManager::get()->stopScript(scriptUrl.trimmed(), false)) { - qCWarning(commerce) << "Couldn't stop script during app uninstall. Continuing anyway. ScriptURL is:" << scriptUrl.trimmed(); + qCWarning(commerce) << "Couldn't stop script during app uninstall. Continuing anyway. ScriptURL is:" + << scriptUrl.trimmed(); } // Delete the .app.json from the filesystem // remove() closes the file first. if (!appFile.remove()) { - qCWarning(commerce) << "Couldn't delete local .app.json file during app uninstall. Continuing anyway. App filename is:" << appHref.fileName(); + qCWarning(commerce) << "Couldn't delete local .app.json file during app uninstall. Continuing anyway. App filename is:" + << appHref.fileName(); } QFileInfo appFileInfo(appFile); @@ -352,7 +360,8 @@ bool QmlCommerce::openApp(const QString& itemHref) { QJsonObject appFileJsonObject = appFileJsonDocument.object(); QString homeUrl = appFileJsonObject["homeURL"].toString(); - auto tablet = dynamic_cast(DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); + auto tablet = dynamic_cast( + DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); if (homeUrl.contains(".qml", Qt::CaseInsensitive)) { tablet->loadQMLSource(homeUrl); } else if (homeUrl.contains(".html", Qt::CaseInsensitive)) { @@ -377,7 +386,7 @@ void QmlCommerce::updateItem(const QString& certificateId) { auto wallet = DependencyManager::get(); QStringList keys = wallet->listPublicKeys(); if (keys.count() == 0) { - QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + QJsonObject result{ { "status", "fail" }, { "message", "Uninitialized Wallet." } }; return emit updateItemResult(result); } QString key = keys[0]; diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index 4bc6817a9e..0d991fc9bc 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -16,7 +16,6 @@ #include "Application.h" #include "Menu.h" #include "SceneScriptingInterface.h" -#include "SafeLanding.h" OctreePacketProcessor::OctreePacketProcessor(): _safeLanding(new SafeLanding()) @@ -133,7 +132,3 @@ void OctreePacketProcessor::processPacket(QSharedPointer messag void OctreePacketProcessor::startEntitySequence() { _safeLanding->startEntitySequence(qApp->getEntities()); } - -bool OctreePacketProcessor::isLoadSequenceComplete() const { - return _safeLanding->isLoadSequenceComplete(); -} diff --git a/interface/src/octree/OctreePacketProcessor.h b/interface/src/octree/OctreePacketProcessor.h index f9c24ddc51..d6ffb942e6 100644 --- a/interface/src/octree/OctreePacketProcessor.h +++ b/interface/src/octree/OctreePacketProcessor.h @@ -15,7 +15,7 @@ #include #include -class SafeLanding; +#include "SafeLanding.h" /// Handles processing of incoming voxel packets for the interface application. As with other ReceivedPacketProcessor classes /// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket() @@ -26,7 +26,8 @@ public: ~OctreePacketProcessor(); void startEntitySequence(); - bool isLoadSequenceComplete() const; + bool isLoadSequenceComplete() const { return _safeLanding->isLoadSequenceComplete(); } + float domainLoadingProgress() const { return _safeLanding->loadingProgressPercentage(); } signals: void packetVersionMismatch(); @@ -40,4 +41,4 @@ private slots: private: std::unique_ptr _safeLanding; }; -#endif // hifi_OctreePacketProcessor_h +#endif // hifi_OctreePacketProcessor_h diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index 60b660f66a..5d4ebe9853 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -13,6 +13,7 @@ #include "EntityTreeRenderer.h" #include "ModelEntityItem.h" #include "InterfaceLogging.h" +#include "Application.h" const int SafeLanding::SEQUENCE_MODULO = std::numeric_limits::max() + 1; @@ -53,6 +54,7 @@ void SafeLanding::startEntitySequence(QSharedPointer entityT void SafeLanding::stopEntitySequence() { Locker lock(_lock); _trackingEntities = false; + _maxTrackedEntityCount = 0; _initialStart = INVALID_SEQUENCE; _initialEnd = INVALID_SEQUENCE; _trackedEntities.clear(); @@ -64,21 +66,18 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { Locker lock(_lock); EntityItemPointer entity = _entityTree->findEntityByID(entityID); - if (entity && !entity->getCollisionless()) { - const auto& entityType = entity->getType(); - if (entityType == EntityTypes::Model) { - ModelEntityItem * modelEntity = std::dynamic_pointer_cast(entity).get(); - static const std::set downloadedCollisionTypes - { SHAPE_TYPE_COMPOUND, SHAPE_TYPE_SIMPLE_COMPOUND, SHAPE_TYPE_STATIC_MESH, SHAPE_TYPE_SIMPLE_HULL }; - bool hasAABox; - entity->getAABox(hasAABox); - if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { - // Only track entities with downloaded collision bodies. - _trackedEntities.emplace(entityID, entity); - qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); - } + if (entity) { + + _trackedEntities.emplace(entityID, entity); + int trackedEntityCount = (int)_trackedEntities.size(); + + if (trackedEntityCount > _maxTrackedEntityCount) { + _maxTrackedEntityCount = trackedEntityCount; } + qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); } + } else { + qCDebug(interfaceapp) << "Safe Landing: Null Entity: " << entityID; } } @@ -103,19 +102,27 @@ void SafeLanding::noteReceivedsequenceNumber(int sequenceNumber) { } bool SafeLanding::isLoadSequenceComplete() { - if (isEntityPhysicsComplete() && isSequenceNumbersComplete()) { + if (isEntityLoadingComplete() && isSequenceNumbersComplete()) { Locker lock(_lock); _trackedEntities.clear(); _initialStart = INVALID_SEQUENCE; _initialEnd = INVALID_SEQUENCE; _entityTree = nullptr; EntityTreeRenderer::setEntityLoadingPriorityFunction(StandardPriority); - qCDebug(interfaceapp) << "Safe Landing: load sequence complete"; } return !_trackingEntities; } +float SafeLanding::loadingProgressPercentage() { + Locker lock(_lock); + if (_maxTrackedEntityCount > 0) { + return ((_maxTrackedEntityCount - _trackedEntities.size()) / (float)_maxTrackedEntityCount); + } + + return 0.0f; +} + bool SafeLanding::isSequenceNumbersComplete() { if (_initialStart != INVALID_SEQUENCE) { Locker lock(_lock); @@ -134,17 +141,53 @@ bool SafeLanding::isSequenceNumbersComplete() { return false; } -bool SafeLanding::isEntityPhysicsComplete() { - Locker lock(_lock); - for (auto entityMapIter = _trackedEntities.begin(); entityMapIter != _trackedEntities.end(); ++entityMapIter) { - auto entity = entityMapIter->second; - if (!entity->shouldBePhysical() || entity->isReadyToComputeShape()) { - entityMapIter = _trackedEntities.erase(entityMapIter); - if (entityMapIter == _trackedEntities.end()) { - break; +bool isEntityPhysicsReady(const EntityItemPointer& entity) { + if (entity && !entity->getCollisionless()) { + const auto& entityType = entity->getType(); + if (entityType == EntityTypes::Model) { + ModelEntityItem * modelEntity = std::dynamic_pointer_cast(entity).get(); + static const std::set downloadedCollisionTypes + { SHAPE_TYPE_COMPOUND, SHAPE_TYPE_SIMPLE_COMPOUND, SHAPE_TYPE_STATIC_MESH, SHAPE_TYPE_SIMPLE_HULL }; + bool hasAABox; + entity->getAABox(hasAABox); + if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { + return (!entity->shouldBePhysical() || entity->isReadyToComputeShape()); } } } + + return true; +} + +bool SafeLanding::isEntityLoadingComplete() { + Locker lock(_lock); + + + auto entityTree = qApp->getEntities(); + auto entityMapIter = _trackedEntities.begin(); + + while (entityMapIter != _trackedEntities.end()) { + auto entity = entityMapIter->second; + + bool isVisuallyReady = true; + + Settings settings; + bool enableInterstitial = settings.value("enableIntersitialMode", false).toBool(); + + if (enableInterstitial) { + isVisuallyReady = (entity->isVisuallyReady() || !entityTree->renderableForEntityId(entityMapIter->first)); + } + + if (isEntityPhysicsReady(entity) && isVisuallyReady) { + entityMapIter = _trackedEntities.erase(entityMapIter); + } else { + if (!isVisuallyReady) { + entity->requestRenderUpdate(); + } + + entityMapIter++; + } + } return _trackedEntities.empty(); } diff --git a/interface/src/octree/SafeLanding.h b/interface/src/octree/SafeLanding.h index 9177930d81..317e4587c7 100644 --- a/interface/src/octree/SafeLanding.h +++ b/interface/src/octree/SafeLanding.h @@ -18,6 +18,7 @@ #include #include "EntityItem.h" +#include "EntityDynamicInterface.h" class EntityTreeRenderer; class EntityItemID; @@ -29,6 +30,7 @@ public: void setCompletionSequenceNumbers(int first, int last); // 'last' exclusive. void noteReceivedsequenceNumber(int sequenceNumber); bool isLoadSequenceComplete(); + float loadingProgressPercentage(); private slots: void addTrackedEntity(const EntityItemID& entityID); @@ -37,7 +39,7 @@ private slots: private: bool isSequenceNumbersComplete(); void debugDumpSequenceIDs() const; - bool isEntityPhysicsComplete(); + bool isEntityLoadingComplete(); std::mutex _lock; using Locker = std::lock_guard; @@ -49,6 +51,7 @@ private: static constexpr int INVALID_SEQUENCE = -1; int _initialStart { INVALID_SEQUENCE }; int _initialEnd { INVALID_SEQUENCE }; + int _maxTrackedEntityCount { 0 }; struct SequenceLessThan { bool operator()(const int& a, const int& b) const; diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index afbb278dc7..5fbe3a90b5 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -73,6 +73,9 @@ glm::vec3 LaserPointer::getPickOrigin(const PickResultPointer& pickResult) const glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const { auto rayPickResult = std::static_pointer_cast(pickResult); + if (!rayPickResult) { + return glm::vec3(0.0f); + } if (distance > 0.0f) { PickRay pick = PickRay(rayPickResult->pickVariant); return pick.origin + distance * pick.direction; diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp index a2a9211b88..e8aa42140b 100644 --- a/interface/src/raypick/ParabolaPick.cpp +++ b/interface/src/raypick/ParabolaPick.cpp @@ -13,6 +13,7 @@ #include "avatar/AvatarManager.h" #include "scripting/HMDScriptingInterface.h" #include "DependencyManager.h" +#include "PickManager.h" ParabolaPick::ParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool rotateAccelerationWithParent, bool scaleWithParent, const PickFilter& filter, float maxDistance, bool enabled) : Pick(filter, maxDistance, enabled), @@ -53,8 +54,9 @@ PickParabola ParabolaPick::getMathematicalPick() const { PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) { if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get()->getForceCoarsePicking()); ParabolaToEntityIntersectionResult entityRes = - DependencyManager::get()->findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(), + DependencyManager::get()->findParabolaIntersectionVector(pick, precisionPicking, getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); if (entityRes.intersects) { return std::make_shared(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo); @@ -65,8 +67,9 @@ PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) { if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get()->getForceCoarsePicking()); ParabolaToOverlayIntersectionResult overlayRes = - qApp->getOverlays().findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(), + qApp->getOverlays().findParabolaIntersectionVector(pick, precisionPicking, getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); if (overlayRes.intersects) { return std::make_shared(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.parabolicDistance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo); diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index fc3aecdb51..ad698c409b 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -96,6 +96,9 @@ glm::vec3 ParabolaPointer::getPickOrigin(const PickResultPointer& pickResult) co glm::vec3 ParabolaPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const { auto parabolaPickResult = std::static_pointer_cast(pickResult); + if (!parabolaPickResult) { + return glm::vec3(0.0f); + } if (distance > 0.0f) { PickParabola pick = PickParabola(parabolaPickResult->pickVariant); return pick.origin + pick.velocity * distance + 0.5f * pick.acceleration * distance * distance; @@ -379,9 +382,8 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::updateBounds() { const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline() { if (!_parabolaPipeline || !_transparentParabolaPipeline) { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola); - { + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola); auto state = std::make_shared(); state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setBlendFunction(false, @@ -393,6 +395,7 @@ const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::get } { + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola_translucent); auto state = std::make_shared(); state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setBlendFunction(true, diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp index 685611d77b..0301136bfa 100644 --- a/interface/src/raypick/PathPointer.cpp +++ b/interface/src/raypick/PathPointer.cpp @@ -54,11 +54,13 @@ PathPointer::~PathPointer() { void PathPointer::setRenderState(const std::string& state) { withWriteLock([&] { if (!_currentRenderState.empty() && state != _currentRenderState) { - if (_renderStates.find(_currentRenderState) != _renderStates.end()) { - _renderStates[_currentRenderState]->disable(); + auto renderState = _renderStates.find(_currentRenderState); + if (renderState != _renderStates.end()) { + renderState->second->disable(); } - if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { - _defaultRenderStates[_currentRenderState].second->disable(); + auto defaultRenderState = _defaultRenderStates.find(_currentRenderState); + if (defaultRenderState != _defaultRenderStates.end()) { + defaultRenderState->second.second->disable(); } } _currentRenderState = state; @@ -105,7 +107,7 @@ PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pick glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition()); glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat; pos = extractTranslation(finalPosAndRotMat); - rot = glmExtractRotation(finalPosAndRotMat); + rot = props.getRotation(); dim = props.getDimensions(); registrationPoint = props.getRegistrationPoint(); } @@ -142,52 +144,57 @@ PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pick void PathPointer::updateVisuals(const PickResultPointer& pickResult) { IntersectionType type = getPickedObjectType(pickResult); - if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && + auto renderState = _renderStates.find(_currentRenderState); + auto defaultRenderState = _defaultRenderStates.find(_currentRenderState); + if (_enabled && !_currentRenderState.empty() && renderState != _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(); + renderState->second->update(origin, end, surfaceNormal, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, + _followNormal, _followNormalStrength, _pathLength, pickResult); + if (defaultRenderState != _defaultRenderStates.end() && defaultRenderState->second.second->isEnabled()) { + defaultRenderState->second.second->disable(); } - } else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { - if (_renderStates.find(_currentRenderState) != _renderStates.end()) { - _renderStates[_currentRenderState]->disable(); + } else if (_enabled && !_currentRenderState.empty() && defaultRenderState != _defaultRenderStates.end()) { + if (renderState != _renderStates.end() && renderState->second->isEnabled()) { + renderState->second->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); + glm::vec3 end = getPickEnd(pickResult, defaultRenderState->second.first); + defaultRenderState->second.second->update(origin, end, Vectors::UP, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, + _faceAvatar, _followNormal, _followNormalStrength, defaultRenderState->second.first, pickResult); } else if (!_currentRenderState.empty()) { - if (_renderStates.find(_currentRenderState) != _renderStates.end()) { - _renderStates[_currentRenderState]->disable(); + if (renderState != _renderStates.end() && renderState->second->isEnabled()) { + renderState->second->disable(); } - if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { - _defaultRenderStates[_currentRenderState].second->disable(); + if (defaultRenderState != _defaultRenderStates.end() && defaultRenderState->second.second->isEnabled()) { + defaultRenderState->second.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)); - } + auto renderState = _renderStates.find(state); + if (renderState != _renderStates.end()) { + updateRenderStateOverlay(renderState->second->getStartID(), startProps); + updateRenderStateOverlay(renderState->second->getEndID(), endProps); + QVariant startDim = startProps.toMap()["dimensions"]; + if (startDim.isValid()) { + renderState->second->setStartDim(vec3FromVariant(startDim)); + } + QVariant endDim = endProps.toMap()["dimensions"]; + if (endDim.isValid()) { + renderState->second->setEndDim(vec3FromVariant(endDim)); + } + QVariant rotation = endProps.toMap()["rotation"]; + if (rotation.isValid()) { + renderState->second->setEndRot(quatFromVariant(rotation)); + } - editRenderStatePath(state, pathProps); + editRenderStatePath(state, pathProps); + } }); } @@ -271,6 +278,7 @@ void StartEndRenderState::disable() { endProps.insert("ignoreRayIntersection", true); qApp->getOverlays().editOverlay(getEndID(), endProps); } + _enabled = false; } void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, @@ -337,6 +345,7 @@ void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, endProps.insert("ignoreRayIntersection", doesEndIgnoreRays()); qApp->getOverlays().editOverlay(getEndID(), endProps); } + _enabled = true; } glm::vec2 PathPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) { @@ -350,4 +359,4 @@ glm::vec2 PathPointer::findPos2D(const PickedObject& pickedObject, const glm::ve default: return glm::vec2(NAN); } -} \ No newline at end of file +} diff --git a/interface/src/raypick/PathPointer.h b/interface/src/raypick/PathPointer.h index 44c1b7f82b..b3638d1f7d 100644 --- a/interface/src/raypick/PathPointer.h +++ b/interface/src/raypick/PathPointer.h @@ -47,6 +47,8 @@ public: 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); + bool isEnabled() const { return _enabled; } + protected: OverlayID _startID; OverlayID _endID; @@ -59,6 +61,8 @@ protected: glm::quat _avgEndRot; bool _avgEndRotInitialized { false }; + + bool _enabled { true }; }; typedef std::unordered_map> RenderStateMap; diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index d16e10a3f4..07e583ea1b 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -13,6 +13,7 @@ #include "avatar/AvatarManager.h" #include "scripting/HMDScriptingInterface.h" #include "DependencyManager.h" +#include "PickManager.h" PickRay RayPick::getMathematicalPick() const { if (!parentTransform) { @@ -26,8 +27,9 @@ PickRay RayPick::getMathematicalPick() const { } PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) { + bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get()->getForceCoarsePicking()); RayToEntityIntersectionResult entityRes = - DependencyManager::get()->findRayIntersectionVector(pick, !getFilter().doesPickCoarse(), + DependencyManager::get()->findRayIntersectionVector(pick, precisionPicking, getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); if (entityRes.intersects) { return std::make_shared(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo); @@ -37,8 +39,9 @@ PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) { } PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) { + bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get()->getForceCoarsePicking()); RayToOverlayIntersectionResult overlayRes = - qApp->getOverlays().findRayIntersectionVector(pick, !getFilter().doesPickCoarse(), + qApp->getOverlays().findRayIntersectionVector(pick, precisionPicking, getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); if (overlayRes.intersects) { return std::make_shared(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo); diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index d963b0c670..06e3e52d21 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -64,7 +64,9 @@ void StylusPointer::updateVisuals(const PickResultPointer& pickResult) { return; } } - hide(); + if (_showing) { + hide(); + } } void StylusPointer::show(const StylusTip& tip) { @@ -80,6 +82,7 @@ void StylusPointer::show(const StylusTip& tip) { props["visible"] = true; qApp->getOverlays().editOverlay(_stylusOverlay, props); } + _showing = true; } void StylusPointer::hide() { @@ -88,6 +91,7 @@ void StylusPointer::hide() { props.insert("visible", false); qApp->getOverlays().editOverlay(_stylusOverlay, props); } + _showing = false; } bool StylusPointer::shouldHover(const PickResultPointer& pickResult) { diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h index 1fe8b047e5..4095acb529 100644 --- a/interface/src/raypick/StylusPointer.h +++ b/interface/src/raypick/StylusPointer.h @@ -78,6 +78,8 @@ 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); + bool _showing { true }; + }; #endif // hifi_StylusPointer_h diff --git a/interface/src/scripting/TestScriptingInterface.cpp b/interface/src/scripting/TestScriptingInterface.cpp index 430441226f..52f6a3ebc0 100644 --- a/interface/src/scripting/TestScriptingInterface.cpp +++ b/interface/src/scripting/TestScriptingInterface.cpp @@ -190,4 +190,12 @@ void TestScriptingInterface::saveObject(QVariant variant, const QString& filenam void TestScriptingInterface::showMaximized() { qApp->getWindow()->showMaximized(); +} + +void TestScriptingInterface::setOtherAvatarsReplicaCount(int count) { + qApp->setOtherAvatarsReplicaCount(count); +} + +int TestScriptingInterface::getOtherAvatarsReplicaCount() { + return qApp->getOtherAvatarsReplicaCount(); } \ No newline at end of file diff --git a/interface/src/scripting/TestScriptingInterface.h b/interface/src/scripting/TestScriptingInterface.h index c47e39d1f3..4a1d1a3eeb 100644 --- a/interface/src/scripting/TestScriptingInterface.h +++ b/interface/src/scripting/TestScriptingInterface.h @@ -149,6 +149,20 @@ public slots: */ void showMaximized(); + /**jsdoc + * Values higher than 0 will create replicas of other-avatars when entering a domain for testing purpouses + * @function Test.setOtherAvatarsReplicaCount + * @param {number} count - Number of replicas we want to create + */ + Q_INVOKABLE void setOtherAvatarsReplicaCount(int count); + + /**jsdoc + * Return the number of replicas that are being created of other-avatars when entering a domain + * @function Test.getOtherAvatarsReplicaCount + * @returns {number} Current number of replicas of other-avatars. + */ + Q_INVOKABLE int getOtherAvatarsReplicaCount(); + private: bool waitForCondition(qint64 maxWaitMs, std::function condition); QString _testResultsLocation; diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index ba86925581..d4eb37e0aa 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -180,6 +180,14 @@ void WindowScriptingInterface::setPreviousBrowseAssetLocation(const QString& loc Setting::Handle(LAST_BROWSE_ASSETS_LOCATION_SETTING).set(location); } +bool WindowScriptingInterface::getInterstitialModeEnabled() const { + return DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled(); +} + +void WindowScriptingInterface::setInterstitialModeEnabled(bool enableInterstitialMode) { + DependencyManager::get()->getDomainHandler().setInterstitialModeEnabled(enableInterstitialMode); +} + bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) { auto offscreenUi = DependencyManager::get(); return offscreenUi->isPointOnDesktopWindow(point); @@ -409,6 +417,10 @@ glm::vec2 WindowScriptingInterface::getDeviceSize() const { return qApp->getDeviceSize(); } +int WindowScriptingInterface::getLastDomainConnectionError() const { + return DependencyManager::get()->getDomainHandler().getLastDomainConnectionError(); +} + int WindowScriptingInterface::getX() { return qApp->getWindow()->geometry().x(); } @@ -584,3 +596,8 @@ void WindowScriptingInterface::onMessageBoxSelected(int button) { _messageBoxes.remove(id); } } + + +float WindowScriptingInterface::domainLoadingProgress() { + return qApp->getOctreePacketProcessor().domainLoadingProgress(); +} diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 77895e0e76..ddd7159f23 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -49,6 +49,7 @@ class WindowScriptingInterface : public QObject, public Dependency { Q_PROPERTY(int innerHeight READ getInnerHeight) Q_PROPERTY(int x READ getX) Q_PROPERTY(int y READ getY) + Q_PROPERTY(bool interstitialModeEnabled READ getInterstitialModeEnabled WRITE setInterstitialModeEnabled) public: WindowScriptingInterface(); @@ -491,6 +492,13 @@ public slots: */ glm::vec2 getDeviceSize() const; + /**jsdoc + * Gets the last domain connection error when a connection is refused. + * @function Window.getLastDomainConnectionError + * @returns {Window.ConnectionRefusedReason} Integer number that enumerates the last domain connection refused. + */ + int getLastDomainConnectionError() const; + /**jsdoc * Open a non-modal message box that can have a variety of button combinations. See also, * {@link Window.updateMessageBox|updateMessageBox} and {@link Window.closeMessageBox|closeMessageBox}. @@ -561,6 +569,8 @@ public slots: */ void closeMessageBox(int id); + float domainLoadingProgress(); + private slots: void onWindowGeometryChanged(const QRect& geometry); void onMessageBoxSelected(int button); @@ -749,6 +759,9 @@ private: QString getPreviousBrowseAssetLocation() const; void setPreviousBrowseAssetLocation(const QString& location); + bool getInterstitialModeEnabled() const; + void setInterstitialModeEnabled(bool enableInterstitialMode); + void ensureReticleVisible() const; int createMessageBox(QString title, QString text, int buttons, int defaultButton); diff --git a/interface/src/ui/AnimStats.cpp b/interface/src/ui/AnimStats.cpp new file mode 100644 index 0000000000..e3579fa2dc --- /dev/null +++ b/interface/src/ui/AnimStats.cpp @@ -0,0 +1,164 @@ +// +// Created by Anthony J. Thibault 2018/08/06 +// 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 "AnimStats.h" + +#include +#include +#include "Menu.h" + +HIFI_QML_DEF(AnimStats) + +static AnimStats* INSTANCE{ nullptr }; + +AnimStats* AnimStats::getInstance() { + Q_ASSERT(INSTANCE); + return INSTANCE; +} + +AnimStats::AnimStats(QQuickItem* parent) : QQuickItem(parent) { + INSTANCE = this; +} + +void AnimStats::updateStats(bool force) { + QQuickItem* parent = parentItem(); + if (!force) { + if (!Menu::getInstance()->isOptionChecked(MenuOption::AnimStats)) { + if (parent->isVisible()) { + parent->setVisible(false); + } + return; + } else if (!parent->isVisible()) { + parent->setVisible(true); + } + } + + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager->getMyAvatar(); + auto debugAlphaMap = myAvatar->getSkeletonModel()->getRig().getDebugAlphaMap(); + + glm::vec3 position = myAvatar->getWorldPosition(); + glm::quat rotation = myAvatar->getWorldOrientation(); + glm::vec3 velocity = myAvatar->getWorldVelocity(); + + _positionText = QString("Position: (%1, %2, %3)"). + arg(QString::number(position.x, 'f', 2)). + arg(QString::number(position.y, 'f', 2)). + arg(QString::number(position.z, 'f', 2)); + emit positionTextChanged(); + + glm::vec3 eulerRotation = safeEulerAngles(rotation); + _rotationText = QString("Heading: %1"). + arg(QString::number(glm::degrees(eulerRotation.y), 'f', 2)); + emit rotationTextChanged(); + + // transform velocity into rig coordinate frame. z forward. + glm::vec3 localVelocity = Quaternions::Y_180 * glm::inverse(rotation) * velocity; + _velocityText = QString("Local Vel: (%1, %2, %3)"). + arg(QString::number(localVelocity.x, 'f', 2)). + arg(QString::number(localVelocity.y, 'f', 2)). + arg(QString::number(localVelocity.z, 'f', 2)); + emit velocityTextChanged(); + + // update animation debug alpha values + QStringList newAnimAlphaValues; + qint64 now = usecTimestampNow(); + for (auto& iter : debugAlphaMap) { + QString key = iter.first; + float alpha = std::get<0>(iter.second); + + auto prevIter = _prevDebugAlphaMap.find(key); + if (prevIter != _prevDebugAlphaMap.end()) { + float prevAlpha = std::get<0>(iter.second); + if (prevAlpha != alpha) { + // change detected: reset timer + _animAlphaValueChangedTimers[key] = now; + } + } else { + // new value: start timer + _animAlphaValueChangedTimers[key] = now; + } + + AnimNodeType type = std::get<1>(iter.second); + if (type == AnimNodeType::Clip) { + + // figure out the grayScale color of this line. + const float LIT_TIME = 2.0f; + const float FADE_OUT_TIME = 1.0f; + float grayScale = 0.0f; + float secondsElapsed = (float)(now - _animAlphaValueChangedTimers[key]) / (float)USECS_PER_SECOND; + if (secondsElapsed < LIT_TIME) { + grayScale = 1.0f; + } else if (secondsElapsed < LIT_TIME + FADE_OUT_TIME) { + grayScale = (FADE_OUT_TIME - (secondsElapsed - LIT_TIME)) / FADE_OUT_TIME; + } else { + grayScale = 0.0f; + } + + if (grayScale > 0.0f) { + // append grayScaleColor to start of debug string + newAnimAlphaValues << QString::number(grayScale, 'f', 2) + "|" + key + ": " + QString::number(alpha, 'f', 3); + } + } + } + + _animAlphaValues = newAnimAlphaValues; + _prevDebugAlphaMap = debugAlphaMap; + + emit animAlphaValuesChanged(); + + // update animation anim vars + _animVarsList.clear(); + auto animVars = myAvatar->getSkeletonModel()->getRig().getAnimVars().toDebugMap(); + for (auto& iter : animVars) { + QString key = iter.first; + QString value = iter.second; + + auto prevIter = _prevAnimVars.find(key); + if (prevIter != _prevAnimVars.end()) { + QString prevValue = prevIter->second; + if (value != prevValue) { + // change detected: reset timer + _animVarChangedTimers[key] = now; + } + } else { + // new value: start timer + _animVarChangedTimers[key] = now; + } + + // figure out the grayScale color of this line. + const float LIT_TIME = 2.0f; + const float FADE_OUT_TIME = 0.5f; + float grayScale = 0.0f; + float secondsElapsed = (float)(now - _animVarChangedTimers[key]) / (float)USECS_PER_SECOND; + if (secondsElapsed < LIT_TIME) { + grayScale = 1.0f; + } else if (secondsElapsed < LIT_TIME + FADE_OUT_TIME) { + grayScale = (FADE_OUT_TIME - (secondsElapsed - LIT_TIME)) / FADE_OUT_TIME; + } else { + grayScale = 0.0f; + } + + if (grayScale > 0.0f) { + // append grayScaleColor to start of debug string + _animVarsList << QString::number(grayScale, 'f', 2) + "|" + key + ": " + value; + } + } + _prevAnimVars = animVars; + emit animVarsChanged(); + + // animation state machines + _animStateMachines.clear(); + auto stateMachineMap = myAvatar->getSkeletonModel()->getRig().getStateMachineMap(); + for (auto& iter : stateMachineMap) { + _animStateMachines << iter.second; + } + emit animStateMachinesChanged(); +} + + diff --git a/interface/src/ui/AnimStats.h b/interface/src/ui/AnimStats.h new file mode 100644 index 0000000000..7b6aaf7b54 --- /dev/null +++ b/interface/src/ui/AnimStats.h @@ -0,0 +1,69 @@ +// +// Created by Anthony J. Thibault 2018/08/06 +// 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_AnimStats_h +#define hifi_AnimStats_h + +#include +#include + +class AnimStats : public QQuickItem { + Q_OBJECT + HIFI_QML_DECL + + Q_PROPERTY(QStringList animAlphaValues READ animAlphaValues NOTIFY animAlphaValuesChanged) + Q_PROPERTY(QStringList animVars READ animVars NOTIFY animVarsChanged) + Q_PROPERTY(QStringList animStateMachines READ animStateMachines NOTIFY animStateMachinesChanged) + Q_PROPERTY(QString positionText READ positionText NOTIFY positionTextChanged) + Q_PROPERTY(QString rotationText READ rotationText NOTIFY rotationTextChanged) + Q_PROPERTY(QString velocityText READ velocityText NOTIFY velocityTextChanged) + +public: + static AnimStats* getInstance(); + + AnimStats(QQuickItem* parent = nullptr); + + void updateStats(bool force = false); + + QStringList animAlphaValues() const { return _animAlphaValues; } + QStringList animVars() const { return _animVarsList; } + QStringList animStateMachines() const { return _animStateMachines; } + + QString positionText() const { return _positionText; } + QString rotationText() const { return _rotationText; } + QString velocityText() const { return _velocityText; } + +public slots: + void forceUpdateStats() { updateStats(true); } + +signals: + + void animAlphaValuesChanged(); + void animVarsChanged(); + void animStateMachinesChanged(); + void positionTextChanged(); + void rotationTextChanged(); + void velocityTextChanged(); + +private: + QStringList _animAlphaValues; + AnimContext::DebugAlphaMap _prevDebugAlphaMap; // alpha values from previous frame + std::map _animAlphaValueChangedTimers; // last time alpha value has changed + + QStringList _animVarsList; + std::map _prevAnimVars; // anim vars from previous frame + std::map _animVarChangedTimers; // last time animVar value has changed. + + QStringList _animStateMachines; + + QString _positionText; + QString _rotationText; + QString _velocityText; +}; + +#endif // hifi_AnimStats_h diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 8a40ee2f83..43da6ea863 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "AccountManager.h" #include "DependencyManager.h" @@ -37,11 +38,19 @@ LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) { connect(accountManager.data(), &AccountManager::loginFailed, this, &LoginDialog::handleLoginFailed); #endif - } -void LoginDialog::showWithSelection() -{ +LoginDialog::~LoginDialog() { + Setting::Handle loginDialogPoppedUp{ "loginDialogPoppedUp", false }; + if (loginDialogPoppedUp.get()) { + QJsonObject data; + data["action"] = "user opted out"; + UserActivityLogger::getInstance().logAction("encourageLoginDialog", data); + } + loginDialogPoppedUp.set(false); +} + +void LoginDialog::showWithSelection() { auto tabletScriptingInterface = DependencyManager::get(); auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); auto hmd = DependencyManager::get(); @@ -73,9 +82,7 @@ void LoginDialog::toggleAction() { } else { // change the menu item to login loginAction->setText("Login / Sign Up"); - connection = connect(loginAction, &QAction::triggered, [] { - LoginDialog::showWithSelection(); - }); + connection = connect(loginAction, &QAction::triggered, [] { LoginDialog::showWithSelection(); }); } } @@ -158,7 +165,6 @@ void LoginDialog::createAccountFromStream(QString username) { QJsonDocument(payload).toJson()); }); } - } void LoginDialog::openUrl(const QString& url) const { @@ -200,25 +206,24 @@ void LoginDialog::createFailed(QNetworkReply* reply) { } void LoginDialog::signup(const QString& email, const QString& username, const QString& password) { - JSONCallbackParameters callbackParams; callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "signupCompleted"; callbackParams.errorCallbackMethod = "signupFailed"; - + QJsonObject payload; - + QJsonObject userObject; userObject.insert("email", email); userObject.insert("username", username); userObject.insert("password", password); - + payload.insert("user", userObject); - + static const QString API_SIGNUP_PATH = "api/v1/users"; - + qDebug() << "Sending a request to create an account for" << username; - + auto accountManager = DependencyManager::get(); accountManager->sendRequest(API_SIGNUP_PATH, AccountManagerAuth::None, QNetworkAccessManager::PostOperation, callbackParams, @@ -240,41 +245,37 @@ QString errorStringFromAPIObject(const QJsonValue& apiObject) { } void LoginDialog::signupFailed(QNetworkReply* reply) { - // parse the returned JSON to see what the problem was auto jsonResponse = QJsonDocument::fromJson(reply->readAll()); - + static const QString RESPONSE_DATA_KEY = "data"; - + auto dataJsonValue = jsonResponse.object()[RESPONSE_DATA_KEY]; - + if (dataJsonValue.isObject()) { auto dataObject = dataJsonValue.toObject(); - + static const QString EMAIL_DATA_KEY = "email"; static const QString USERNAME_DATA_KEY = "username"; static const QString PASSWORD_DATA_KEY = "password"; - + QStringList errorStringList; - + if (dataObject.contains(EMAIL_DATA_KEY)) { errorStringList.append(QString("Email %1.").arg(errorStringFromAPIObject(dataObject[EMAIL_DATA_KEY]))); } - + if (dataObject.contains(USERNAME_DATA_KEY)) { errorStringList.append(QString("Username %1.").arg(errorStringFromAPIObject(dataObject[USERNAME_DATA_KEY]))); } - + if (dataObject.contains(PASSWORD_DATA_KEY)) { errorStringList.append(QString("Password %1.").arg(errorStringFromAPIObject(dataObject[PASSWORD_DATA_KEY]))); } - + emit handleSignupFailed(errorStringList.join('\n')); } else { static const QString DEFAULT_SIGN_UP_FAILURE_MESSAGE = "There was an unknown error while creating your account. Please try again later."; emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE); } - - } - diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index ad8cab9699..ae069f9ab1 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -27,7 +27,10 @@ public: LoginDialog(QQuickItem* parent = nullptr); + ~LoginDialog(); + static void showWithSelection(); + signals: void handleLoginCompleted(); void handleLoginFailed(); @@ -62,7 +65,6 @@ protected slots: Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password); Q_INVOKABLE void openUrl(const QString& url) const; - }; #endif // hifi_LoginDialog_h diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 79ca2063ec..951925214c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -55,53 +55,12 @@ void setupPreferences() { // Graphics quality static const QString GRAPHICS_QUALITY { "Graphics Quality" }; { - static const float MAX_DESKTOP_FPS = 60; - static const float MAX_HMD_FPS = 90; - static const float MIN_FPS = 10; - static const float LOW = 0.25f; - static const float MEDIUM = 0.5f; - static const float HIGH = 0.75f; auto getter = []()->float { - auto lodManager = DependencyManager::get(); - bool inHMD = qApp->isHMDMode(); - - float increaseFPS = 0; - if (inHMD) { - increaseFPS = lodManager->getHMDLODDecreaseFPS(); - } else { - increaseFPS = lodManager->getDesktopLODDecreaseFPS(); - } - float maxFPS = inHMD ? MAX_HMD_FPS : MAX_DESKTOP_FPS; - float percentage = increaseFPS / maxFPS; - - if (percentage >= HIGH) { - return LOW; - } else if (percentage >= LOW) { - return MEDIUM; - } - return HIGH; + return DependencyManager::get()->getWorldDetailQuality(); }; auto setter = [](float value) { - static const float THRASHING_DIFFERENCE = 10; - auto lodManager = DependencyManager::get(); - - bool isLowestValue = value == LOW; - bool isHMDMode = qApp->isHMDMode(); - - float maxFPS = isHMDMode ? MAX_HMD_FPS : MAX_DESKTOP_FPS; - float desiredFPS = maxFPS - THRASHING_DIFFERENCE; - - if (!isLowestValue) { - float calculatedFPS = (maxFPS - (maxFPS * value)) - THRASHING_DIFFERENCE; - desiredFPS = calculatedFPS < MIN_FPS ? MIN_FPS : calculatedFPS; - } - - if (isHMDMode) { - lodManager->setHMDLODDecreaseFPS(desiredFPS); - } else { - lodManager->setDesktopLODDecreaseFPS(desiredFPS); - } + DependencyManager::get()->setWorldDetailQuality(value); }; auto wodSlider = new SliderPreference(GRAPHICS_QUALITY, "World Detail", getter, setter); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 557635469b..8faa51bc58 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -105,6 +106,10 @@ extern std::atomic DECIMATED_TEXTURE_COUNT; extern std::atomic RECTIFIED_TEXTURE_COUNT; void Stats::updateStats(bool force) { + + if (qApp->isInterstitialMode()) { + return; + } QQuickItem* parent = parentItem(); if (!force) { if (!Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { @@ -147,6 +152,20 @@ void Stats::updateStats(bool force) { } STAT_UPDATE(gameLoopRate, (int)qApp->getGameLoopRate()); + auto pickManager = DependencyManager::get(); + if (pickManager && (_expanded || force)) { + std::vector totalPicks = pickManager->getTotalPickCounts(); + STAT_UPDATE(stylusPicksCount, totalPicks[PickQuery::Stylus]); + STAT_UPDATE(rayPicksCount, totalPicks[PickQuery::Ray]); + STAT_UPDATE(parabolaPicksCount, totalPicks[PickQuery::Parabola]); + STAT_UPDATE(collisionPicksCount, totalPicks[PickQuery::Collision]); + std::vector updatedPicks = pickManager->getUpdatedPickCounts(); + STAT_UPDATE(stylusPicksUpdated, updatedPicks[PickQuery::Stylus]); + STAT_UPDATE(rayPicksUpdated, updatedPicks[PickQuery::Ray]); + STAT_UPDATE(parabolaPicksUpdated, updatedPicks[PickQuery::Parabola]); + STAT_UPDATE(collisionPicksUpdated, updatedPicks[PickQuery::Collision]); + } + auto bandwidthRecorder = DependencyManager::get(); STAT_UPDATE(packetInCount, (int)bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond()); STAT_UPDATE(packetOutCount, (int)bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond()); @@ -192,14 +211,6 @@ void Stats::updateStats(bool force) { // Third column, avatar stats auto myAvatar = avatarManager->getMyAvatar(); - auto animStack = myAvatar->getSkeletonModel()->getRig().getAnimStack(); - - _animStackNames.clear(); - for (auto animStackIterator = animStack.begin(); animStackIterator != animStack.end(); ++animStackIterator) { - _animStackNames << animStackIterator->first + ": " + QString::number(animStackIterator->second,'f',3); - } - emit animStackNamesChanged(); - glm::vec3 avatarPos = myAvatar->getWorldPosition(); STAT_UPDATE(position, QVector3D(avatarPos.x, avatarPos.y, avatarPos.z)); STAT_UPDATE_FLOAT(speed, glm::length(myAvatar->getWorldVelocity()), 0.01f); @@ -286,7 +297,7 @@ void Stats::updateStats(bool force) { // downloads << (int)(resource->getProgress() * 100.0f) << "% "; //} //downloads << "(" << << " pending)"; - } // expanded avatar column + } // Fourth column, octree stats int serverCount = 0; @@ -355,27 +366,35 @@ void Stats::updateStats(bool force) { STAT_UPDATE(engineFrameTime, (float) config->getCPURunTime()); STAT_UPDATE(avatarSimulationTime, (float)avatarManager->getAvatarSimulationTime()); - STAT_UPDATE(gpuBuffers, (int)gpu::Context::getBufferGPUCount()); - STAT_UPDATE(gpuBufferMemory, (int)BYTES_TO_MB(gpu::Context::getBufferGPUMemSize())); - STAT_UPDATE(gpuTextures, (int)gpu::Context::getTextureGPUCount()); + if (_expanded) { + STAT_UPDATE(gpuBuffers, (int)gpu::Context::getBufferGPUCount()); + STAT_UPDATE(gpuBufferMemory, (int)BYTES_TO_MB(gpu::Context::getBufferGPUMemSize())); + STAT_UPDATE(gpuTextures, (int)gpu::Context::getTextureGPUCount()); - STAT_UPDATE(glContextSwapchainMemory, (int)BYTES_TO_MB(gl::Context::getSwapchainMemoryUsage())); + STAT_UPDATE(glContextSwapchainMemory, (int)BYTES_TO_MB(gl::Context::getSwapchainMemoryUsage())); - STAT_UPDATE(qmlTextureMemory, (int)BYTES_TO_MB(OffscreenQmlSurface::getUsedTextureMemory())); - STAT_UPDATE(texturePendingTransfers, (int)BYTES_TO_MB(gpu::Context::getTexturePendingGPUTransferMemSize())); - STAT_UPDATE(gpuTextureMemory, (int)BYTES_TO_MB(gpu::Context::getTextureGPUMemSize())); - STAT_UPDATE(gpuTextureResidentMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResidentGPUMemSize())); - STAT_UPDATE(gpuTextureFramebufferMemory, (int)BYTES_TO_MB(gpu::Context::getTextureFramebufferGPUMemSize())); - STAT_UPDATE(gpuTextureResourceMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceGPUMemSize())); - STAT_UPDATE(gpuTextureResourceIdealMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceIdealGPUMemSize())); - STAT_UPDATE(gpuTextureResourcePopulatedMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourcePopulatedGPUMemSize())); - STAT_UPDATE(gpuTextureExternalMemory, (int)BYTES_TO_MB(gpu::Context::getTextureExternalGPUMemSize())); + STAT_UPDATE(qmlTextureMemory, (int)BYTES_TO_MB(OffscreenQmlSurface::getUsedTextureMemory())); + STAT_UPDATE(texturePendingTransfers, (int)BYTES_TO_MB(gpu::Context::getTexturePendingGPUTransferMemSize())); + STAT_UPDATE(gpuTextureMemory, (int)BYTES_TO_MB(gpu::Context::getTextureGPUMemSize())); + STAT_UPDATE(gpuTextureResidentMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResidentGPUMemSize())); + STAT_UPDATE(gpuTextureFramebufferMemory, (int)BYTES_TO_MB(gpu::Context::getTextureFramebufferGPUMemSize())); + STAT_UPDATE(gpuTextureResourceMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceGPUMemSize())); + STAT_UPDATE(gpuTextureResourceIdealMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceIdealGPUMemSize())); + STAT_UPDATE(gpuTextureResourcePopulatedMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourcePopulatedGPUMemSize())); + STAT_UPDATE(gpuTextureExternalMemory, (int)BYTES_TO_MB(gpu::Context::getTextureExternalGPUMemSize())); #if !defined(Q_OS_ANDROID) - STAT_UPDATE(gpuTextureMemoryPressureState, getTextureMemoryPressureModeString()); + STAT_UPDATE(gpuTextureMemoryPressureState, getTextureMemoryPressureModeString()); #endif - STAT_UPDATE(gpuFreeMemory, (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemSize())); - STAT_UPDATE(rectifiedTextureCount, (int)RECTIFIED_TEXTURE_COUNT.load()); - STAT_UPDATE(decimatedTextureCount, (int)DECIMATED_TEXTURE_COUNT.load()); + STAT_UPDATE(gpuFreeMemory, (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemSize())); + STAT_UPDATE(rectifiedTextureCount, (int)RECTIFIED_TEXTURE_COUNT.load()); + STAT_UPDATE(decimatedTextureCount, (int)DECIMATED_TEXTURE_COUNT.load()); + } + + gpu::ContextStats gpuFrameStats; + gpuContext->getFrameStats(gpuFrameStats); + + STAT_UPDATE(drawcalls, gpuFrameStats._DSNumDrawcalls); + // Incoming packets QLocale locale(QLocale::English); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 84a114b72d..ae608cfddb 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -22,7 +22,6 @@ public: \ private: \ type _##name{ initialValue }; - /**jsdoc * @namespace Stats * @@ -94,7 +93,6 @@ private: \ * @property {number} processing - Read-only. * @property {number} processingPending - Read-only. * @property {number} triangles - Read-only. - * @property {number} quads - Read-only. * @property {number} materialSwitches - Read-only. * @property {number} itemConsidered - Read-only. * @property {number} itemOutOfView - Read-only. @@ -136,7 +134,6 @@ private: \ * @property {number} batchFrameTime - Read-only. * @property {number} engineFrameTime - Read-only. * @property {number} avatarSimulationTime - Read-only. - * @property {string[]} animStackNames - Read-only. * * * @property {number} x @@ -169,6 +166,15 @@ private: \ * @property {number} implicitHeight * * @property {object} layer - Read-only. + + * @property {number} stylusPicksCount - Read-only. + * @property {number} rayPicksCount - Read-only. + * @property {number} parabolaPicksCount - Read-only. + * @property {number} collisionPicksCount - Read-only. + * @property {Vec4} stylusPicksUpdated - Read-only. + * @property {Vec4} rayPicksUpdated - Read-only. + * @property {Vec4} parabolaPicksUpdated - Read-only. + * @property {Vec4} collisionPicksUpdated - Read-only. */ // Properties from x onwards are QQuickItem properties. @@ -241,7 +247,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, processing, 0) STATS_PROPERTY(int, processingPending, 0) STATS_PROPERTY(int, triangles, 0) - STATS_PROPERTY(int, quads, 0) + STATS_PROPERTY(int, drawcalls, 0) STATS_PROPERTY(int, materialSwitches, 0) STATS_PROPERTY(int, itemConsidered, 0) STATS_PROPERTY(int, itemOutOfView, 0) @@ -285,7 +291,15 @@ class Stats : public QQuickItem { STATS_PROPERTY(float, batchFrameTime, 0) STATS_PROPERTY(float, engineFrameTime, 0) STATS_PROPERTY(float, avatarSimulationTime, 0) - Q_PROPERTY(QStringList animStackNames READ animStackNames NOTIFY animStackNamesChanged) + + STATS_PROPERTY(int, stylusPicksCount, 0) + STATS_PROPERTY(int, rayPicksCount, 0) + STATS_PROPERTY(int, parabolaPicksCount, 0) + STATS_PROPERTY(int, collisionPicksCount, 0) + STATS_PROPERTY(QVector4D, stylusPicksUpdated, QVector4D(0, 0, 0, 0)) + STATS_PROPERTY(QVector4D, rayPicksUpdated, QVector4D(0, 0, 0, 0)) + STATS_PROPERTY(QVector4D, parabolaPicksUpdated, QVector4D(0, 0, 0, 0)) + STATS_PROPERTY(QVector4D, collisionPicksUpdated, QVector4D(0, 0, 0, 0)) public: static Stats* getInstance(); @@ -310,7 +324,6 @@ public: } QStringList downloadUrls () { return _downloadUrls; } - QStringList animStackNames() { return _animStackNames; } public slots: void forceUpdateStats() { updateStats(true); } @@ -718,11 +731,12 @@ signals: void trianglesChanged(); /**jsdoc - * Triggered when the value of the quads property changes. - * @function Stats.quadsChanged - * @returns {Signal} - */ - void quadsChanged(); + * Triggered when the value of the drawcalls property changes. + * This + * @function Stats.drawcallsChanged + * @returns {Signal} + */ + void drawcallsChanged(); /**jsdoc * Triggered when the value of the materialSwitches property changes. @@ -1011,13 +1025,6 @@ signals: */ void avatarSimulationTimeChanged(); - /**jsdoc - * Triggered when the value of the animStackNames property changes. - * @function Stats.animStackNamesChanged - * @returns {Signal} - */ - void animStackNamesChanged(); - /**jsdoc * Triggered when the value of the rectifiedTextureCount property changes. * @function Stats.rectifiedTextureCountChanged @@ -1032,7 +1039,6 @@ signals: */ void decimatedTextureCountChanged(); - // QQuickItem signals. /**jsdoc @@ -1254,6 +1260,62 @@ signals: * @function Stats.update */ + /**jsdoc + * Triggered when the value of the stylusPicksCount property changes. + * @function Stats.stylusPicksCountChanged + * @returns {Signal} + */ + void stylusPicksCountChanged(); + + /**jsdoc + * Triggered when the value of the rayPicksCount property changes. + * @function Stats.rayPicksCountChanged + * @returns {Signal} + */ + void rayPicksCountChanged(); + + /**jsdoc + * Triggered when the value of the parabolaPicksCount property changes. + * @function Stats.parabolaPicksCountChanged + * @returns {Signal} + */ + void parabolaPicksCountChanged(); + + /**jsdoc + * Triggered when the value of the collisionPicksCount property changes. + * @function Stats.collisionPicksCountChanged + * @returns {Signal} + */ + void collisionPicksCountChanged(); + + /**jsdoc + * Triggered when the value of the stylusPicksUpdated property changes. + * @function Stats.stylusPicksUpdatedChanged + * @returns {Signal} + */ + void stylusPicksUpdatedChanged(); + + /**jsdoc + * Triggered when the value of the rayPicksUpdated property changes. + * @function Stats.rayPicksUpdatedChanged + * @returns {Signal} + */ + void rayPicksUpdatedChanged(); + + /**jsdoc + * Triggered when the value of the parabolaPicksUpdated property changes. + * @function Stats.parabolaPicksUpdatedChanged + * @returns {Signal} + */ + void parabolaPicksUpdatedChanged(); + + /**jsdoc + * Triggered when the value of the collisionPicksUpdated property changes. + * @function Stats.collisionPicksUpdatedChanged + * @returns {Signal} + */ + void collisionPicksUpdatedChanged(); + private: int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process bool _resetRecentMaxPacketsSoon{ true }; @@ -1263,7 +1325,6 @@ private: QString _monospaceFont; const AudioIOStats* _audioStats; QStringList _downloadUrls = QStringList(); - QStringList _animStackNames = QStringList(); }; #endif // hifi_Stats_h diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 6bce9d9283..1d8db69e26 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -238,7 +238,9 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { */ QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "name") { - return _name; + return _nameLock.resultWithReadLock([&] { + return _name; + }); } if (property == "position" || property == "start" || property == "p1" || property == "point") { return vec3toVariant(getWorldPosition()); @@ -346,6 +348,20 @@ void Base3DOverlay::setVisible(bool visible) { notifyRenderVariableChange(); } +QString Base3DOverlay::getName() const { + return _nameLock.resultWithReadLock([&] { + return QString("Overlay:") + _name; + }); +} + +void Base3DOverlay::setName(QString name) { + _nameLock.withWriteLock([&] { + _name = name; + }); +} + + + render::ItemKey Base3DOverlay::getKey() { auto builder = render::ItemKey::Builder(Overlay::getKey()); @@ -364,4 +380,4 @@ render::ItemKey Base3DOverlay::getKey() { } return builder.build(); -} \ No newline at end of file +} diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index d44c193055..6f6092a42e 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -29,8 +29,8 @@ public: virtual OverlayID getOverlayID() const override { return OverlayID(getID().toString()); } void setOverlayID(OverlayID overlayID) override { setID(overlayID); } - virtual QString getName() const override { return QString("Overlay:") + _name; } - void setName(QString name) { _name = name; } + virtual QString getName() const override; + void setName(QString name); // getters virtual bool is3D() const override { return true; } @@ -107,6 +107,7 @@ protected: mutable bool _renderVariableDirty { true }; QString _name; + mutable ReadWriteLockable _nameLock; }; #endif // hifi_Base3DOverlay_h diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 789b1f9969..5de8bb1a2a 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -180,7 +180,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& float distance; BoxFace face; glm::vec3 normal; - boundingBox.findRayIntersection(cameraPosition, direction, distance, face, normal); + boundingBox.findRayIntersection(cameraPosition, direction, 1.0f / direction, distance, face, normal); float offsetAngle = -CONTEXT_OVERLAY_OFFSET_ANGLE; if (event.getID() == 1) { // "1" is left hand offsetAngle *= -1.0f; @@ -252,12 +252,6 @@ bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityIt void ContextOverlayInterface::contextOverlays_mousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { if (overlayID == _contextOverlayID && event.getButton() == PointerEvent::PrimaryButton) { qCDebug(context_overlay) << "Clicked Context Overlay. Entity ID:" << _currentEntityWithContextOverlay << "Overlay ID:" << overlayID; - Setting::Handle _settingSwitch{ "commerce", true }; - if (_settingSwitch.get()) { - openInspectionCertificate(); - } else { - openMarketplace(); - } emit contextOverlayClicked(_currentEntityWithContextOverlay); _contextOverlayJustClicked = true; } @@ -390,34 +384,6 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID } } -static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; -void ContextOverlayInterface::openInspectionCertificate() { - // lets open the tablet to the inspection certificate QML - if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) { - setLastInspectedEntity(_currentEntityWithContextOverlay); - auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); - tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH); - _hmdScriptingInterface->openTablet(); - } -} - -static const QString MARKETPLACE_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/"; - -void ContextOverlayInterface::openMarketplace() { - // lets open the tablet and go to the current item in - // the marketplace (if the current entity has a - // marketplaceID) - if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) { - auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); - // construct the url to the marketplace item - QString url = MARKETPLACE_BASE_URL + _entityMarketplaceID; - QString MARKETPLACES_INJECT_SCRIPT_PATH = "file:///" + qApp->applicationDirPath() + "/scripts/system/html/js/marketplacesInject.js"; - tablet->gotoWebScreen(url, MARKETPLACES_INJECT_SCRIPT_PATH); - _hmdScriptingInterface->openTablet(); - _isInMarketplaceInspectionMode = true; - } -} - void ContextOverlayInterface::enableEntityHighlight(const EntityItemID& entityItemID) { _selectionScriptingInterface->addToSelectedItemsList("contextOverlayHighlightList", "entity", entityItemID); } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 808c3a4ee3..48b14e1a91 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -94,8 +94,6 @@ private: bool _isInMarketplaceInspectionMode { false }; - void openInspectionCertificate(); - void openMarketplace(); void enableEntityHighlight(const EntityItemID& entityItemID); void disableEntityHighlight(const EntityItemID& entityItemID); diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index c87650a77b..a307d445c0 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -88,7 +88,7 @@ bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::ve // we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame // and testing intersection there. - bool hit = _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal); + bool hit = _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, 1.0f / overlayFrameDirection, distance, face, surfaceNormal); if (hit) { surfaceNormal = transform.getRotation() * surfaceNormal; diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index c16b4c016d..9d55c91ef3 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -132,7 +132,9 @@ void Web3DOverlay::destroyWebSurface() { if (rootItem && rootItem->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); + if (tabletScriptingInterface) { + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); + } } // Fix for crash in QtWebEngineCore when rapidly switching domains diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp index e2d79f864d..17ca88123f 100644 --- a/libraries/animation/src/AnimBlendLinear.cpp +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -27,7 +27,7 @@ AnimBlendLinear::~AnimBlendLinear() { const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { _alpha = animVars.lookup(_alphaVar, _alpha); - float parentAlpha = _animStack[_id]; + float parentDebugAlpha = context.getDebugAlpha(_id); if (_children.size() == 0) { for (auto&& pose : _poses) { @@ -35,7 +35,7 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, con } } else if (_children.size() == 1) { _poses = _children[0]->evaluate(animVars, context, dt, triggersOut); - _animStack[_children[0]->getID()] = parentAlpha; + context.setDebugAlpha(_children[0]->getID(), parentDebugAlpha, _children[0]->getType()); } else { float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); size_t prevPoseIndex = glm::floor(clampedAlpha); @@ -48,12 +48,12 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, con float weight2 = 0.0f; if (prevPoseIndex == nextPoseIndex) { weight2 = 1.0f; - _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + context.setDebugAlpha(_children[nextPoseIndex]->getID(), weight2 * parentDebugAlpha, _children[nextPoseIndex]->getType()); } else { weight2 = alpha; weight1 = 1.0f - weight2; - _animStack[_children[prevPoseIndex]->getID()] = weight1 * parentAlpha; - _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + context.setDebugAlpha(_children[prevPoseIndex]->getID(), weight1 * parentDebugAlpha, _children[prevPoseIndex]->getType()); + context.setDebugAlpha(_children[nextPoseIndex]->getID(), weight2 * parentDebugAlpha, _children[nextPoseIndex]->getType()); } } processOutputJoints(triggersOut); diff --git a/libraries/animation/src/AnimBlendLinearMove.cpp b/libraries/animation/src/AnimBlendLinearMove.cpp index 6313d4cbe9..07e1c17f77 100644 --- a/libraries/animation/src/AnimBlendLinearMove.cpp +++ b/libraries/animation/src/AnimBlendLinearMove.cpp @@ -62,9 +62,7 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, speed = animVars.lookup("moveForwardSpeed", speed); } _alpha = calculateAlpha(speed, _characteristicSpeeds); - float parentAlpha = _animStack[_id]; - - _animStack["speed"] = speed; + float parentDebugAlpha = context.getDebugAlpha(_id); if (_children.size() == 0) { for (auto&& pose : _poses) { @@ -77,7 +75,7 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, float prevDeltaTime, nextDeltaTime; setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut); evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime); - _animStack[_children[0]->getID()] = parentAlpha; + context.setDebugAlpha(_children[0]->getID(), parentDebugAlpha, _children[0]->getType()); } else { auto clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); auto prevPoseIndex = glm::floor(clampedAlpha); @@ -87,17 +85,11 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut); evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime); - // weights are for animation stack debug purposes only. - float weight1 = 0.0f; - float weight2 = 0.0f; if (prevPoseIndex == nextPoseIndex) { - weight2 = 1.0f; - _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + context.setDebugAlpha(_children[nextPoseIndex]->getID(), parentDebugAlpha, _children[nextPoseIndex]->getType()); } else { - weight2 = alpha; - weight1 = 1.0f - weight2; - _animStack[_children[prevPoseIndex]->getID()] = weight1 * parentAlpha; - _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + context.setDebugAlpha(_children[prevPoseIndex]->getID(), (1.0f - alpha) * parentDebugAlpha, _children[prevPoseIndex]->getType()); + context.setDebugAlpha(_children[nextPoseIndex]->getID(), alpha * parentDebugAlpha, _children[nextPoseIndex]->getType()); } } @@ -156,6 +148,10 @@ void AnimBlendLinearMove::setFrameAndPhase(float dt, float alpha, int prevPoseIn // integrate phase forward in time. _phase += omega * dt; + if (_phase < 0.0f) { + _phase = 0.0f; + } + // detect loop trigger events if (_phase >= 1.0f) { triggersOut.setTrigger(_id + "Loop"); diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index e8bf5c34eb..c455dd9c8f 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -14,8 +14,27 @@ #include #include +#include +#include +#include + +enum class AnimNodeType { + Clip = 0, + BlendLinear, + BlendLinearMove, + Overlay, + StateMachine, + Manipulator, + InverseKinematics, + DefaultPose, + TwoBoneIK, + PoleVectorConstraint, + NumTypes +}; + class AnimContext { public: + AnimContext() {} AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, bool enableDebugDrawIKChains, const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix); @@ -25,6 +44,39 @@ public: const glm::mat4& getGeometryToRigMatrix() const { return _geometryToRigMatrix; } const glm::mat4& getRigToWorldMatrix() const { return _rigToWorldMatrix; } + float getDebugAlpha(const QString& key) const { + auto it = _debugAlphaMap.find(key); + if (it != _debugAlphaMap.end()) { + return std::get<0>(it->second); + } else { + return 1.0f; + } + } + + using DebugAlphaMapValue = std::tuple; + using DebugAlphaMap = std::map; + + void setDebugAlpha(const QString& key, float alpha, AnimNodeType type) const { + _debugAlphaMap[key] = DebugAlphaMapValue(alpha, type); + } + + const DebugAlphaMap& getDebugAlphaMap() const { + return _debugAlphaMap; + } + + using DebugStateMachineMapValue = QString; + using DebugStateMachineMap = std::map; + + void addStateMachineInfo(const QString& stateMachineName, const QString& currentState, const QString& previousState, bool duringInterp, float alpha) const { + if (duringInterp) { + _stateMachineMap[stateMachineName] = QString("%1: %2 -> %3 (%4)").arg(stateMachineName).arg(previousState).arg(currentState).arg(QString::number(alpha, 'f', 2)); + } else { + _stateMachineMap[stateMachineName] = QString("%1: %2").arg(stateMachineName).arg(currentState); + } + } + + const DebugStateMachineMap& getStateMachineMap() const { return _stateMachineMap; } + protected: bool _enableDebugDrawIKTargets { false }; @@ -32,6 +84,10 @@ protected: bool _enableDebugDrawIKChains { false }; glm::mat4 _geometryToRigMatrix; glm::mat4 _rigToWorldMatrix; + + // used for debugging internal state of animation system. + mutable DebugAlphaMap _debugAlphaMap; + mutable DebugStateMachineMap _stateMachineMap; }; #endif // hifi_AnimContext_h diff --git a/libraries/animation/src/AnimNode.cpp b/libraries/animation/src/AnimNode.cpp index a8e76617ae..f055e6b473 100644 --- a/libraries/animation/src/AnimNode.cpp +++ b/libraries/animation/src/AnimNode.cpp @@ -12,10 +12,6 @@ #include -std::map AnimNode::_animStack = { - {"none", 0.0f} -}; - AnimNode::Pointer AnimNode::getParent() { return _parent.lock(); } diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 1f14889ce4..1a12bb8ddb 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -36,19 +36,7 @@ class QJsonObject; class AnimNode : public std::enable_shared_from_this { public: - enum class Type { - Clip = 0, - BlendLinear, - BlendLinearMove, - Overlay, - StateMachine, - Manipulator, - InverseKinematics, - DefaultPose, - TwoBoneIK, - PoleVectorConstraint, - NumTypes - }; + using Type = AnimNodeType; using Pointer = std::shared_ptr; using ConstPointer = std::shared_ptr; @@ -84,7 +72,6 @@ public: } void setCurrentFrame(float frame); - const std::map getAnimStack() { return _animStack; } template bool traverse(F func) { @@ -127,9 +114,6 @@ protected: std::weak_ptr _parent; std::vector _outputJointNames; - // global available to Stats.h - static std::map _animStack; - // no copies AnimNode(const AnimNode&) = delete; AnimNode& operator=(const AnimNode&) = delete; diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 90fd425ae5..fb13b8e71c 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -23,9 +23,7 @@ AnimStateMachine::~AnimStateMachine() { const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { - if (_id.contains("userAnimStateMachine")) { - _animStack.clear(); - } + float parentDebugAlpha = context.getDebugAlpha(_id); QString desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID()); if (_currentState->getID() != desiredStateID) { @@ -33,8 +31,6 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co bool foundState = false; for (auto& state : _states) { if (state->getID() == desiredStateID) { - // parenthesis means previous state, which is a snapshot. - _previousStateID = "(" + _currentState->getID() + ")"; switchState(animVars, context, state); foundState = true; break; @@ -48,8 +44,6 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co // evaluate currentState transitions auto desiredState = evaluateTransitions(animVars); if (desiredState != _currentState) { - // parenthesis means previous state, which is a snapshot. - _previousStateID = "(" + _currentState->getID() + ")"; switchState(animVars, context, desiredState); } @@ -57,17 +51,8 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co auto currentStateNode = _children[_currentState->getChildIndex()]; assert(currentStateNode); - if (!_previousStateID.contains("none")) { - _animStack[_previousStateID] = 1.0f - _alpha; - } - if (_duringInterp) { _alpha += _alphaVel * dt; - if (_alpha > 1.0f) { - _animStack[_currentState->getID()] = 1.0f; - } else { - _animStack[_currentState->getID()] = _alpha; - } if (_alpha < 1.0f) { AnimPoseVec* nextPoses = nullptr; AnimPoseVec* prevPoses = nullptr; @@ -88,26 +73,31 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co if (_poses.size() > 0 && nextPoses && prevPoses && nextPoses->size() > 0 && prevPoses->size() > 0) { ::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), _alpha, &_poses[0]); } + context.setDebugAlpha(_currentState->getID(), _alpha * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); } else { _duringInterp = false; - if (_animStack.count(_previousStateID) > 0) { - _animStack.erase(_previousStateID); - } - _previousStateID = "none"; _prevPoses.clear(); _nextPoses.clear(); } } + if (!_duringInterp) { - _animStack[_currentState->getID()] = 1.0f; + context.setDebugAlpha(_currentState->getID(), parentDebugAlpha, _children[_currentState->getChildIndex()]->getType()); _poses = currentStateNode->evaluate(animVars, context, dt, triggersOut); } processOutputJoints(triggersOut); + context.addStateMachineInfo(_id, _currentState->getID(), _previousState->getID(), _duringInterp, _alpha); + if (_duringInterp) { + // hack: add previoius state to debug alpha map, with parens around it's name. + context.setDebugAlpha(QString("(%1)").arg(_previousState->getID()), 1.0f - _alpha, AnimNodeType::Clip); + } + return _poses; } void AnimStateMachine::setCurrentState(State::Pointer state) { + _previousState = _currentState ? _currentState : state; _currentState = state; } @@ -152,7 +142,7 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, const AnimCon qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget << "interpType = " << (int)_interpType; #endif - _currentState = desiredState; + setCurrentState(desiredState); } AnimStateMachine::State::Pointer AnimStateMachine::evaluateTransitions(const AnimVariantMap& animVars) const { diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index b20e5203a1..713c659a1d 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -138,9 +138,9 @@ protected: float _alpha = 0.0f; AnimPoseVec _prevPoses; AnimPoseVec _nextPoses; - QString _previousStateID { "none" }; State::Pointer _currentState; + State::Pointer _previousState; std::vector _states; QString _currentStateVar; diff --git a/libraries/animation/src/AnimVariant.cpp b/libraries/animation/src/AnimVariant.cpp index 483a7999c9..21fe234f7b 100644 --- a/libraries/animation/src/AnimVariant.cpp +++ b/libraries/animation/src/AnimVariant.cpp @@ -67,6 +67,7 @@ QScriptValue AnimVariantMap::animVariantMapToScriptValue(QScriptEngine* engine, } return target; } + void AnimVariantMap::copyVariantsFrom(const AnimVariantMap& other) { for (auto& pair : other._map) { _map[pair.first] = pair.second; @@ -124,3 +125,52 @@ void AnimVariantMap::animVariantMapFromScriptValue(const QScriptValue& source) { } } } + +std::map AnimVariantMap::toDebugMap() const { + std::map result; + for (auto& pair : _map) { + switch (pair.second.getType()) { + case AnimVariant::Type::Bool: + result[pair.first] = QString("%1").arg(pair.second.getBool()); + break; + case AnimVariant::Type::Int: + result[pair.first] = QString("%1").arg(pair.second.getInt()); + break; + case AnimVariant::Type::Float: + result[pair.first] = QString::number(pair.second.getFloat(), 'f', 3); + break; + case AnimVariant::Type::Vec3: { + // To prevent filling up debug stats, don't show vec3 values + /* + glm::vec3 value = pair.second.getVec3(); + result[pair.first] = QString("(%1, %2, %3)"). + arg(QString::number(value.x, 'f', 3)). + arg(QString::number(value.y, 'f', 3)). + arg(QString::number(value.z, 'f', 3)); + */ + break; + } + case AnimVariant::Type::Quat: { + // To prevent filling up the anim stats, don't show quat values + /* + glm::quat value = pair.second.getQuat(); + result[pair.first] = QString("(%1, %2, %3, %4)"). + arg(QString::number(value.x, 'f', 3)). + arg(QString::number(value.y, 'f', 3)). + arg(QString::number(value.z, 'f', 3)). + arg(QString::number(value.w, 'f', 3)); + break; + */ + } + case AnimVariant::Type::String: + // To prevent filling up anim stats, don't show string values + /* + result[pair.first] = pair.second.getString(); + break; + */ + default: + assert(("invalid AnimVariant::Type", false)); + } + } + return result; +} diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index d383b5abb8..0f921984b1 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -235,6 +235,9 @@ public: void animVariantMapFromScriptValue(const QScriptValue& object); void copyVariantsFrom(const AnimVariantMap& other); + // For stat debugging. + std::map toDebugMap() const; + #ifdef NDEBUG void dump() const { qCDebug(animation) << "AnimVariantMap ="; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index c3e679096b..91d4e0f9d3 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -926,7 +926,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos // compute blend based on velocity const float JUMP_SPEED = 3.5f; - float alpha = glm::clamp(-_lastVelocity.y / JUMP_SPEED, -1.0f, 1.0f) + 1.0f; + float alpha = glm::clamp(-workingVelocity.y / JUMP_SPEED, -1.0f, 1.0f) + 1.0f; _animVars.set("inAirAlpha", alpha); } @@ -1061,8 +1061,10 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons // animations haven't fully loaded yet. _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); } + _lastAnimVars = _animVars; _animVars.clearTriggers(); _animVars = triggersOut; + _lastContext = context; } applyOverridePoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index accbcccbbc..48f00d4e5d 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -222,7 +222,10 @@ public: // input assumed to be in rig space void computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const; - const std::map getAnimStack() { return _animNode->getAnimStack(); } + // used to debug animation playback + const AnimContext::DebugAlphaMap& getDebugAlphaMap() const { return _lastContext.getDebugAlphaMap(); } + const AnimVariantMap& getAnimVars() const { return _lastAnimVars; } + const AnimContext::DebugStateMachineMap& getStateMachineMap() const { return _lastContext.getStateMachineMap(); } void toggleSmoothPoleVectors() { _smoothPoleVectors = !_smoothPoleVectors; }; signals: @@ -388,6 +391,9 @@ protected: int _rigId; bool _headEnabled { false }; + + AnimContext _lastContext; + AnimVariantMap _lastAnimVars; }; #endif /* defined(__hifi__Rig__) */ diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 6a9363f309..d00bc29054 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -53,7 +53,6 @@ #include "AudioHelpers.h" #if defined(Q_OS_ANDROID) -#define VOICE_RECOGNITION "voicerecognition" #include #endif @@ -101,6 +100,13 @@ QList getAvailableDevices(QAudio::Mode mode) { // now called from a background thread, to keep blocking operations off the audio thread void AudioClient::checkDevices() { + // Make sure we're not shutting down + Lock timerMutex(_checkDevicesMutex); + // If we HAVE shut down after we were queued, but prior to execution, early exit + if (nullptr == _checkDevicesTimer) { + return; + } + auto inputDevices = getAvailableDevices(QAudio::AudioInput); auto outputDevices = getAvailableDevices(QAudio::AudioOutput); @@ -210,6 +216,7 @@ AudioClient::AudioClient() : _positionGetter(DEFAULT_POSITION_GETTER), #if defined(Q_OS_ANDROID) _checkInputTimer(this), + _isHeadsetPluggedIn(false), #endif _orientationGetter(DEFAULT_ORIENTATION_GETTER) { // avoid putting a lock in the device callback @@ -278,9 +285,6 @@ void AudioClient::customDeleter() { _shouldRestartInputSetup = false; #endif stop(); - _checkDevicesTimer->stop(); - _checkPeakValuesTimer->stop(); - deleteLater(); } @@ -305,6 +309,16 @@ void AudioClient::audioMixerKilled() { emit disconnected(); } +void AudioClient::setAudioPaused(bool pause) { + if (_audioPaused != pause) { + _audioPaused = pause; + + if (!_audioPaused) { + negotiateAudioFormat(); + } + } +} + QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) { QAudioDeviceInfo result; foreach(QAudioDeviceInfo audioDevice, getAvailableDevices(mode)) { @@ -451,9 +465,14 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { #if defined (Q_OS_ANDROID) if (mode == QAudio::AudioInput) { + Setting::Handle enableAEC(SETTING_AEC_KEY, false); + bool aecEnabled = enableAEC.get(); + auto audioClient = DependencyManager::get(); + bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false; auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); for (auto inputDevice : inputDevices) { - if (inputDevice.deviceName() == VOICE_RECOGNITION) { + if (((headsetOn || !aecEnabled) && inputDevice.deviceName() == VOICE_RECOGNITION) || + ((!headsetOn && aecEnabled) && inputDevice.deviceName() == VOICE_COMMUNICATION)) { return inputDevice; } } @@ -638,12 +657,26 @@ void AudioClient::start() { } void AudioClient::stop() { - qCDebug(audioclient) << "AudioClient::stop(), requesting switchInputToAudioDevice() to shut down"; switchInputToAudioDevice(QAudioDeviceInfo(), true); qCDebug(audioclient) << "AudioClient::stop(), requesting switchOutputToAudioDevice() to shut down"; switchOutputToAudioDevice(QAudioDeviceInfo(), true); + + // Stop triggering the checks + QObject::disconnect(_checkPeakValuesTimer, &QTimer::timeout, nullptr, nullptr); + QObject::disconnect(_checkDevicesTimer, &QTimer::timeout, nullptr, nullptr); + + // Destruction of the pointers will occur when the parent object (this) is destroyed) + { + Lock lock(_checkDevicesMutex); + _checkDevicesTimer = nullptr; + } + { + Lock lock(_checkPeakValuesMutex); + _checkPeakValuesTimer = nullptr; + } + #if defined(Q_OS_ANDROID) _checkInputTimer.stop(); disconnect(&_checkInputTimer, &QTimer::timeout, 0, 0); @@ -651,7 +684,6 @@ void AudioClient::stop() { } void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { - char bitset; message->readPrimitive(&bitset); @@ -664,11 +696,10 @@ void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { - if (message->getType() == PacketType::SilentAudioFrame) { _silentInbound.increment(); } else { @@ -1026,80 +1057,82 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { } void AudioClient::handleAudioInput(QByteArray& audioBuffer) { - if (_muted) { - _lastInputLoudness = 0.0f; - _timeSinceLastClip = 0.0f; - } else { - int16_t* samples = reinterpret_cast(audioBuffer.data()); - int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; - int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); - - if (_isNoiseGateEnabled) { - // The audio gate includes DC removal - _audioGate->render(samples, samples, numFrames); - } else { - _audioGate->removeDC(samples, samples, numFrames); - } - - int32_t loudness = 0; - assert(numSamples < 65536); // int32_t loudness cannot overflow - bool didClip = false; - for (int i = 0; i < numSamples; ++i) { - const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); - int32_t sample = std::abs((int32_t)samples[i]); - loudness += sample; - didClip |= (sample > CLIPPING_THRESHOLD); - } - _lastInputLoudness = (float)loudness / numSamples; - - if (didClip) { + if (!_audioPaused) { + if (_muted) { + _lastInputLoudness = 0.0f; _timeSinceLastClip = 0.0f; - } else if (_timeSinceLastClip >= 0.0f) { - _timeSinceLastClip += (float)numSamples / (float)AudioConstants::SAMPLE_RATE; + } else { + int16_t* samples = reinterpret_cast(audioBuffer.data()); + int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; + int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); + + if (_isNoiseGateEnabled) { + // The audio gate includes DC removal + _audioGate->render(samples, samples, numFrames); + } else { + _audioGate->removeDC(samples, samples, numFrames); + } + + int32_t loudness = 0; + assert(numSamples < 65536); // int32_t loudness cannot overflow + bool didClip = false; + for (int i = 0; i < numSamples; ++i) { + const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); + int32_t sample = std::abs((int32_t)samples[i]); + loudness += sample; + didClip |= (sample > CLIPPING_THRESHOLD); + } + _lastInputLoudness = (float)loudness / numSamples; + + if (didClip) { + _timeSinceLastClip = 0.0f; + } else if (_timeSinceLastClip >= 0.0f) { + _timeSinceLastClip += (float)numSamples / (float)AudioConstants::SAMPLE_RATE; + } + + emit inputReceived(audioBuffer); } - emit inputReceived(audioBuffer); + emit inputLoudnessChanged(_lastInputLoudness); + + // state machine to detect gate opening and closing + bool audioGateOpen = (_lastInputLoudness != 0.0f); + bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened + bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed + _audioGateOpen = audioGateOpen; + + if (openedInLastBlock) { + emit noiseGateOpened(); + } else if (closedInLastBlock) { + emit noiseGateClosed(); + } + + // the codec must be flushed to silence before sending silent packets, + // so delay the transition to silent packets by one packet after becoming silent. + auto packetType = _shouldEchoToServer ? PacketType::MicrophoneAudioWithEcho : PacketType::MicrophoneAudioNoEcho; + if (!audioGateOpen && !closedInLastBlock) { + packetType = PacketType::SilentAudioFrame; + _silentOutbound.increment(); + } else { + _audioOutbound.increment(); + } + + Transform audioTransform; + audioTransform.setTranslation(_positionGetter()); + audioTransform.setRotation(_orientationGetter()); + + QByteArray encodedBuffer; + if (_encoder) { + _encoder->encode(audioBuffer, encodedBuffer); + } else { + encodedBuffer = audioBuffer; + } + + emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, _isStereoInput, + audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale, + packetType, _selectedCodecName); + _stats.sentPacket(); } - - emit inputLoudnessChanged(_lastInputLoudness); - - // state machine to detect gate opening and closing - bool audioGateOpen = (_lastInputLoudness != 0.0f); - bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened - bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed - _audioGateOpen = audioGateOpen; - - if (openedInLastBlock) { - emit noiseGateOpened(); - } else if (closedInLastBlock) { - emit noiseGateClosed(); - } - - // the codec must be flushed to silence before sending silent packets, - // so delay the transition to silent packets by one packet after becoming silent. - auto packetType = _shouldEchoToServer ? PacketType::MicrophoneAudioWithEcho : PacketType::MicrophoneAudioNoEcho; - if (!audioGateOpen && !closedInLastBlock) { - packetType = PacketType::SilentAudioFrame; - _silentOutbound.increment(); - } else { - _audioOutbound.increment(); - } - - Transform audioTransform; - audioTransform.setTranslation(_positionGetter()); - audioTransform.setRotation(_orientationGetter()); - - QByteArray encodedBuffer; - if (_encoder) { - _encoder->encode(audioBuffer, encodedBuffer); - } else { - encodedBuffer = audioBuffer; - } - - emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, _isStereoInput, - audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale, - packetType, _selectedCodecName); - _stats.sentPacket(); } void AudioClient::handleMicAudioInput() { @@ -1630,6 +1663,29 @@ void AudioClient::checkInputTimeout() { #endif } +void AudioClient::setHeadsetPluggedIn(bool pluggedIn) { +#if defined(Q_OS_ANDROID) + if (pluggedIn == !_isHeadsetPluggedIn && !_inputDeviceInfo.isNull()) { + QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField("android/os/Build", "BRAND"); + // some samsung phones needs more time to shutdown the previous input device + if (brand.toString().contains("samsung", Qt::CaseInsensitive)) { + switchInputToAudioDevice(QAudioDeviceInfo(), true); + QThread::msleep(200); + } + + Setting::Handle enableAEC(SETTING_AEC_KEY, false); + bool aecEnabled = enableAEC.get(); + + if ((pluggedIn || !aecEnabled) && _inputDeviceInfo.deviceName() != VOICE_RECOGNITION) { + switchAudioDevice(QAudio::AudioInput, VOICE_RECOGNITION); + } else if (!pluggedIn && aecEnabled && _inputDeviceInfo.deviceName() != VOICE_COMMUNICATION) { + switchAudioDevice(QAudio::AudioInput, VOICE_COMMUNICATION); + } + } + _isHeadsetPluggedIn = pluggedIn; +#endif +} + void AudioClient::outputNotify() { int recentUnfulfilled = _audioOutputIODevice.getRecentUnfulfilledReads(); if (recentUnfulfilled > 0) { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 8599c990a3..5e7f1fb8a0 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -64,6 +64,13 @@ #pragma warning( pop ) #endif +#if defined (Q_OS_ANDROID) +#define VOICE_RECOGNITION "voicerecognition" +#define VOICE_COMMUNICATION "voicecommunication" + +#define SETTING_AEC_KEY "Android/aec" +#endif + class QAudioInput; class QAudioOutput; class QIODevice; @@ -162,12 +169,17 @@ public: bool startRecording(const QString& filename); void stopRecording(); + void setAudioPaused(bool pause); #ifdef Q_OS_WIN static QString getWinDeviceName(wchar_t* guid); #endif +#if defined(Q_OS_ANDROID) + bool isHeadsetPluggedIn() { return _isHeadsetPluggedIn; } +#endif + public slots: void start(); void stop(); @@ -216,6 +228,9 @@ public slots: bool switchAudioDevice(QAudio::Mode mode, const QAudioDeviceInfo& deviceInfo = QAudioDeviceInfo()); bool switchAudioDevice(QAudio::Mode mode, const QString& deviceName); + // Qt opensles plugin is not able to detect when the headset is plugged in + void setHeadsetPluggedIn(bool pluggedIn); + float getInputVolume() const { return (_audioInput) ? (float)_audioInput->volume() : 0.0f; } void setInputVolume(float volume, bool emitSignal = true); void setReverb(bool reverb); @@ -277,6 +292,7 @@ private: #ifdef Q_OS_ANDROID QTimer _checkInputTimer; long _inputReadsSinceLastCheck = 0l; + bool _isHeadsetPluggedIn; #endif class Gate { @@ -416,6 +432,7 @@ private: QVector _activeLocalAudioInjectors; bool _isPlayingBackRecording { false }; + bool _audioPaused { false }; CodecPluginPointer _codec; QString _selectedCodecName; @@ -430,7 +447,9 @@ private: bool _shouldRestartInputSetup { true }; // Should we restart the input device because of an unintended stop? #endif + Mutex _checkDevicesMutex; QTimer* _checkDevicesTimer { nullptr }; + Mutex _checkPeakValuesMutex; QTimer* _checkPeakValuesTimer { nullptr }; bool _isRecording { false }; diff --git a/libraries/audio-client/src/AudioPeakValues.cpp b/libraries/audio-client/src/AudioPeakValues.cpp index 0b8921a117..a50567da7f 100644 --- a/libraries/audio-client/src/AudioPeakValues.cpp +++ b/libraries/audio-client/src/AudioPeakValues.cpp @@ -40,6 +40,12 @@ void release(IAudioClient* audioClient) { } void AudioClient::checkPeakValues() { + // Guard against running during shutdown + Lock timerMutex(_checkPeakValuesMutex); + if (nullptr == _checkPeakValuesTimer) { + return; + } + // prepare the windows environment CoInitialize(NULL); diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index 23adcde0c5..b639301404 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -240,7 +240,7 @@ static void FIR_1x4_SSE(float* src, float* dst0, float* dst1, float* dst2, float float* ps = &src[i - HRTF_TAPS + 1]; // process forwards - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { @@ -276,23 +276,8 @@ static void FIR_1x4_SSE(float* src, float* dst0, float* dst1, float* dst2, float } } -// -// Runtime CPU dispatch -// - -#include "CPUDetect.h" - -void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); -void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); - -static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) { - - static auto f = cpuSupportsAVX512() ? FIR_1x4_AVX512 : (cpuSupportsAVX2() ? FIR_1x4_AVX2 : FIR_1x4_SSE); - (*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch -} - // 4 channel planar to interleaved -static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { +static void interleave_4x4_SSE(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { assert(numFrames % 4 == 0); @@ -323,7 +308,7 @@ static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, f // process 2 cascaded biquads on 4 channels (interleaved) // biquads computed in parallel, by adding one sample of delay -static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { +static void biquad2_4x4_SSE(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { // enable flush-to-zero mode to prevent denormals unsigned int ftz = _MM_GET_FLUSH_ZERO_MODE(); @@ -388,7 +373,7 @@ static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3] } // crossfade 4 inputs into 2 outputs with accumulation (interleaved) -static void crossfade_4x2(float* src, float* dst, const float* win, int numFrames) { +static void crossfade_4x2_SSE(float* src, float* dst, const float* win, int numFrames) { assert(numFrames % 4 == 0); @@ -435,12 +420,12 @@ static void crossfade_4x2(float* src, float* dst, const float* win, int numFrame } // linear interpolation with gain -static void interpolate(float* dst, const float* src0, const float* src1, float frac, float gain) { +static void interpolate_SSE(const float* src0, const float* src1, float* dst, float frac, float gain) { __m128 f0 = _mm_set1_ps(gain * (1.0f - frac)); __m128 f1 = _mm_set1_ps(gain * frac); - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { @@ -453,6 +438,44 @@ static void interpolate(float* dst, const float* src0, const float* src1, float } } +// +// Runtime CPU dispatch +// + +#include "CPUDetect.h" + +void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); +void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); +void interleave_4x4_AVX2(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames); +void biquad2_4x4_AVX2(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames); +void crossfade_4x2_AVX2(float* src, float* dst, const float* win, int numFrames); +void interpolate_AVX2(const float* src0, const float* src1, float* dst, float frac, float gain); + +static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) { + static auto f = cpuSupportsAVX512() ? FIR_1x4_AVX512 : (cpuSupportsAVX2() ? FIR_1x4_AVX2 : FIR_1x4_SSE); + (*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch +} + +static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { + static auto f = cpuSupportsAVX2() ? interleave_4x4_AVX2 : interleave_4x4_SSE; + (*f)(src0, src1, src2, src3, dst, numFrames); // dispatch +} + +static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { + static auto f = cpuSupportsAVX2() ? biquad2_4x4_AVX2 : biquad2_4x4_SSE; + (*f)(src, dst, coef, state, numFrames); // dispatch +} + +static void crossfade_4x2(float* src, float* dst, const float* win, int numFrames) { + static auto f = cpuSupportsAVX2() ? crossfade_4x2_AVX2 : crossfade_4x2_SSE; + (*f)(src, dst, win, numFrames); // dispatch +} + +static void interpolate(const float* src0, const float* src1, float* dst, float frac, float gain) { + static auto f = cpuSupportsAVX2() ? interpolate_AVX2 : interpolate_SSE; + (*f)(src0, src1, dst, frac, gain); // dispatch +} + #else // portable reference code // 1 channel input, 4 channel output @@ -489,7 +512,7 @@ static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* ds float* ps = &src[i - HRTF_TAPS + 1]; // process forwards - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { @@ -715,7 +738,7 @@ static void crossfade_4x2(float* src, float* dst, const float* win, int numFrame } // linear interpolation with gain -static void interpolate(float* dst, const float* src0, const float* src1, float frac, float gain) { +static void interpolate(const float* src0, const float* src1, float* dst, float frac, float gain) { float f0 = gain * (1.0f - frac); float f1 = gain * frac; @@ -967,8 +990,8 @@ static void setFilters(float firCoef[4][HRTF_TAPS], float bqCoef[5][8], int dela azimuthToIndex(azimuth, az0, az1, frac); // interpolate FIR - interpolate(firCoef[channel+0], ir_table_table[index][azL0][0], ir_table_table[index][azL1][0], fracL, gain * gainL); - interpolate(firCoef[channel+1], ir_table_table[index][azR0][1], ir_table_table[index][azR1][1], fracR, gain * gainR); + interpolate(ir_table_table[index][azL0][0], ir_table_table[index][azL1][0], firCoef[channel+0], fracL, gain * gainL); + interpolate(ir_table_table[index][azR0][1], ir_table_table[index][azR1][1], firCoef[channel+1], fracR, gain * gainR); // interpolate ITD float itd = (1.0f - frac) * itd_table_table[index][az0] + frac * itd_table_table[index][az1]; diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 67f9952771..da284f19a3 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -43,8 +43,11 @@ void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPo } } -SoundScriptingInterface::SoundScriptingInterface(SharedSoundPointer sound) : _sound(sound) { - QObject::connect(sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready); +SoundScriptingInterface::SoundScriptingInterface(const SharedSoundPointer& sound) : _sound(sound) { + // During shutdown we can sometimes get an empty sound pointer back + if (_sound) { + QObject::connect(_sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready); + } } Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) : diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 348600e4ae..a0544870d0 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -105,11 +105,11 @@ class SoundScriptingInterface : public QObject { Q_PROPERTY(float duration READ getDuration) public: - SoundScriptingInterface(SharedSoundPointer sound); - SharedSoundPointer getSound() { return _sound; } + SoundScriptingInterface(const SharedSoundPointer& sound); + const SharedSoundPointer& getSound() { return _sound; } - bool isReady() const { return _sound->isReady(); } - float getDuration() { return _sound->getDuration(); } + bool isReady() const { return _sound ? _sound->isReady() : false; } + float getDuration() { return _sound ? _sound->getDuration() : 0.0f; } /**jsdoc * Triggered when the sound has been downloaded and is ready to be played. diff --git a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp index e89128b173..aadbb2d0cd 100644 --- a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp +++ b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp @@ -44,7 +44,7 @@ void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3 float* ps = &src[i - HRTF_TAPS + 1]; // process forwards - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { @@ -87,4 +87,165 @@ void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3 _mm256_zeroupper(); } +// 4 channel planar to interleaved +void interleave_4x4_AVX2(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { + + assert(numFrames % 8 == 0); + + for (int i = 0; i < numFrames; i += 8) { + + __m256 x0 = _mm256_loadu_ps(&src0[i]); + __m256 x1 = _mm256_loadu_ps(&src1[i]); + __m256 x2 = _mm256_loadu_ps(&src2[i]); + __m256 x3 = _mm256_loadu_ps(&src3[i]); + + // interleave (4x4 matrix transpose) + __m256 t0 = _mm256_unpacklo_ps(x0, x1); + __m256 t1 = _mm256_unpackhi_ps(x0, x1); + __m256 t2 = _mm256_unpacklo_ps(x2, x3); + __m256 t3 = _mm256_unpackhi_ps(x2, x3); + + x0 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(1,0,1,0)); + x1 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(3,2,3,2)); + x2 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(1,0,1,0)); + x3 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(3,2,3,2)); + + t0 = _mm256_permute2f128_ps(x0, x1, 0x20); + t1 = _mm256_permute2f128_ps(x2, x3, 0x20); + t2 = _mm256_permute2f128_ps(x0, x1, 0x31); + t3 = _mm256_permute2f128_ps(x2, x3, 0x31); + + _mm256_storeu_ps(&dst[4*i+0], t0); + _mm256_storeu_ps(&dst[4*i+8], t1); + _mm256_storeu_ps(&dst[4*i+16], t2); + _mm256_storeu_ps(&dst[4*i+24], t3); + } + + _mm256_zeroupper(); +} + +// process 2 cascaded biquads on 4 channels (interleaved) +// biquads are computed in parallel, by adding one sample of delay +void biquad2_4x4_AVX2(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { + + // enable flush-to-zero mode to prevent denormals + unsigned int ftz = _MM_GET_FLUSH_ZERO_MODE(); + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); + + // restore state + __m256 x0 = _mm256_setzero_ps(); + __m256 y0 = _mm256_loadu_ps(state[0]); + __m256 w1 = _mm256_loadu_ps(state[1]); + __m256 w2 = _mm256_loadu_ps(state[2]); + + // biquad coefs + __m256 b0 = _mm256_loadu_ps(coef[0]); + __m256 b1 = _mm256_loadu_ps(coef[1]); + __m256 b2 = _mm256_loadu_ps(coef[2]); + __m256 a1 = _mm256_loadu_ps(coef[3]); + __m256 a2 = _mm256_loadu_ps(coef[4]); + + for (int i = 0; i < numFrames; i++) { + + // x0 = (first biquad output << 128) | input + x0 = _mm256_insertf128_ps(_mm256_permute2f128_ps(y0, y0, 0x01), _mm_loadu_ps(&src[4*i]), 0); + + // transposed Direct Form II + y0 = _mm256_fmadd_ps(x0, b0, w1); + w1 = _mm256_fmadd_ps(x0, b1, w2); + w2 = _mm256_mul_ps(x0, b2); + w1 = _mm256_fnmadd_ps(y0, a1, w1); + w2 = _mm256_fnmadd_ps(y0, a2, w2); + + _mm_storeu_ps(&dst[4*i], _mm256_extractf128_ps(y0, 1)); // second biquad output + } + + // save state + _mm256_storeu_ps(state[0], y0); + _mm256_storeu_ps(state[1], w1); + _mm256_storeu_ps(state[2], w2); + + _MM_SET_FLUSH_ZERO_MODE(ftz); + _mm256_zeroupper(); +} + +// crossfade 4 inputs into 2 outputs with accumulation (interleaved) +void crossfade_4x2_AVX2(float* src, float* dst, const float* win, int numFrames) { + + assert(numFrames % 8 == 0); + + for (int i = 0; i < numFrames; i += 8) { + + __m256 f0 = _mm256_loadu_ps(&win[i]); + + __m256 x0 = _mm256_castps128_ps256(_mm_loadu_ps(&src[4*i+0])); + __m256 x1 = _mm256_castps128_ps256(_mm_loadu_ps(&src[4*i+4])); + __m256 x2 = _mm256_castps128_ps256(_mm_loadu_ps(&src[4*i+8])); + __m256 x3 = _mm256_castps128_ps256(_mm_loadu_ps(&src[4*i+12])); + + x0 = _mm256_insertf128_ps(x0, _mm_loadu_ps(&src[4*i+16]), 1); + x1 = _mm256_insertf128_ps(x1, _mm_loadu_ps(&src[4*i+20]), 1); + x2 = _mm256_insertf128_ps(x2, _mm_loadu_ps(&src[4*i+24]), 1); + x3 = _mm256_insertf128_ps(x3, _mm_loadu_ps(&src[4*i+28]), 1); + + __m256 y0 = _mm256_loadu_ps(&dst[2*i+0]); + __m256 y1 = _mm256_loadu_ps(&dst[2*i+8]); + + // deinterleave (4x4 matrix transpose) + __m256 t0 = _mm256_unpacklo_ps(x0, x1); + __m256 t1 = _mm256_unpackhi_ps(x0, x1); + __m256 t2 = _mm256_unpacklo_ps(x2, x3); + __m256 t3 = _mm256_unpackhi_ps(x2, x3); + + x0 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(1,0,1,0)); + x1 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(3,2,3,2)); + x2 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(1,0,1,0)); + x3 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(3,2,3,2)); + + // crossfade + x0 = _mm256_sub_ps(x0, x2); + x1 = _mm256_sub_ps(x1, x3); + x2 = _mm256_fmadd_ps(f0, x0, x2); + x3 = _mm256_fmadd_ps(f0, x1, x3); + + // interleave + t0 = _mm256_unpacklo_ps(x2, x3); + t1 = _mm256_unpackhi_ps(x2, x3); + + x0 = _mm256_permute2f128_ps(t0, t1, 0x20); + x1 = _mm256_permute2f128_ps(t0, t1, 0x31); + + // accumulate + y0 = _mm256_add_ps(y0, x0); + y1 = _mm256_add_ps(y1, x1); + + _mm256_storeu_ps(&dst[2*i+0], y0); + _mm256_storeu_ps(&dst[2*i+8], y1); + } + + _mm256_zeroupper(); +} + +// linear interpolation with gain +void interpolate_AVX2(const float* src0, const float* src1, float* dst, float frac, float gain) { + + __m256 f0 = _mm256_set1_ps(gain * (1.0f - frac)); + __m256 f1 = _mm256_set1_ps(gain * frac); + + static_assert(HRTF_TAPS % 8 == 0, "HRTF_TAPS must be a multiple of 8"); + + for (int k = 0; k < HRTF_TAPS; k += 8) { + + __m256 x0 = _mm256_loadu_ps(&src0[k]); + __m256 x1 = _mm256_loadu_ps(&src1[k]); + + x0 = _mm256_mul_ps(f0, x0); + x0 = _mm256_fmadd_ps(f1, x1, x0); + + _mm256_storeu_ps(&dst[k], x0); + } + + _mm256_zeroupper(); +} + #endif diff --git a/libraries/audio/src/avx512/AudioHRTF_avx512.cpp b/libraries/audio/src/avx512/AudioHRTF_avx512.cpp index a8bb62be35..fcec81fa4c 100644 --- a/libraries/audio/src/avx512/AudioHRTF_avx512.cpp +++ b/libraries/audio/src/avx512/AudioHRTF_avx512.cpp @@ -44,7 +44,7 @@ void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* ds float* ps = &src[i - HRTF_TAPS + 1]; // process forwards - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index a9af3b7725..914a3b7c6e 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -126,20 +126,11 @@ Avatar::Avatar(QThread* thread) : _leftPointerGeometryID = geometryCache->allocateID(); _rightPointerGeometryID = geometryCache->allocateID(); _lastRenderUpdateTime = usecTimestampNow(); + + indicateLoadingStatus(LoadingStatus::NoModel); } Avatar::~Avatar() { - auto treeRenderer = DependencyManager::get(); - EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - if (entityTree) { - entityTree->withWriteLock([&] { - AvatarEntityMap avatarEntities = getAvatarEntityData(); - for (auto entityID : avatarEntities.keys()) { - entityTree->deleteEntity(entityID, true, true); - } - }); - } - auto geometryCache = DependencyManager::get(); if (geometryCache) { geometryCache->releaseID(_nameRectGeometryID); @@ -384,6 +375,19 @@ void Avatar::updateAvatarEntities() { setAvatarEntityDataChanged(false); } +void Avatar::removeAvatarEntitiesFromTree() { + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (entityTree) { + entityTree->withWriteLock([&] { + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + entityTree->deleteEntity(entityID, true, true); + } + }); + } +} + void Avatar::relayJointDataToChildren() { forEachChild([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { @@ -1371,12 +1375,15 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const { } void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { - if (!isMyAvatar()) { - createOrb(); - } AvatarData::setSkeletonModelURL(skeletonModelURL); if (QThread::currentThread() == thread()) { + + if (!isMyAvatar()) { + createOrb(); + } + _skeletonModel->setURL(_skeletonModelURL); + indicateLoadingStatus(LoadingStatus::LoadModel); } else { QMetaObject::invokeMethod(_skeletonModel.get(), "setURL", Qt::QueuedConnection, Q_ARG(QUrl, _skeletonModelURL)); } @@ -1389,11 +1396,12 @@ void Avatar::setModelURLFinished(bool success) { _reconstructSoftEntitiesJointMap = true; if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) { + indicateLoadingStatus(LoadingStatus::LoadFailure); const int MAX_SKELETON_DOWNLOAD_ATTEMPTS = 4; // NOTE: we don't want to be as generous as ResourceCache is, we only want 4 attempts if (_skeletonModel->getResourceDownloadAttemptsRemaining() <= 0 || _skeletonModel->getResourceDownloadAttempts() > MAX_SKELETON_DOWNLOAD_ATTEMPTS) { qCWarning(avatars_renderer) << "Using default after failing to load Avatar model: " << _skeletonModelURL - << "after" << _skeletonModel->getResourceDownloadAttempts() << "attempts."; + << "after" << _skeletonModel->getResourceDownloadAttempts() << "attempts."; // call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that // we don't redo this every time we receive an identity packet from the avatar with the bad url. QMetaObject::invokeMethod(_skeletonModel.get(), "setURL", @@ -1404,6 +1412,9 @@ void Avatar::setModelURLFinished(bool success) { << "out of:" << MAX_SKELETON_DOWNLOAD_ATTEMPTS; } } + if (success) { + indicateLoadingStatus(LoadingStatus::LoadSuccess); + } } // rig is ready diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 7d3ec39c4f..4f1c010d84 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -73,6 +73,7 @@ public: void init(); void updateAvatarEntities(); + void removeAvatarEntitiesFromTree(); void simulate(float deltaTime, bool inView); virtual void simulateAttachments(float deltaTime); @@ -107,6 +108,14 @@ public: virtual bool isMyAvatar() const override { return false; } virtual void createOrb() { } + enum class LoadingStatus { + NoModel, + LoadModel, + LoadSuccess, + LoadFailure + }; + virtual void indicateLoadingStatus(LoadingStatus loadingStatus) { _loadingStatus = loadingStatus; } + virtual QVector getJointRotations() const override; using AvatarData::getJointRotation; virtual glm::quat getJointRotation(int index) const override; @@ -458,7 +467,6 @@ protected: glm::vec3 _lastAngularVelocity; glm::vec3 _angularAcceleration; glm::quat _lastOrientation; - glm::vec3 _worldUpDirection { Vectors::UP }; bool _moving { false }; ///< set when position is changing @@ -541,6 +549,8 @@ protected: static const float MYAVATAR_LOADING_PRIORITY; static const float OTHERAVATAR_LOADING_PRIORITY; static const float ATTACHMENT_LOADING_PRIORITY; + + LoadingStatus _loadingStatus { LoadingStatus::NoModel }; }; #endif // hifi_Avatar_h diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index bdee6d9147..9cf034863e 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include #include diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index de8c02f10e..1ec58fd704 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -294,6 +294,15 @@ bool SkeletonModel::getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& firstEyePosition = baseEyePosition + headRotation * glm::vec3(EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight; secondEyePosition = baseEyePosition + headRotation * glm::vec3(-EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight; return true; + } else if (getJointPosition(geometry.headJointIndex, headPosition)) { + glm::vec3 baseEyePosition = headPosition; + glm::quat headRotation; + getJointRotation(geometry.headJointIndex, headRotation); + const float EYES_FORWARD_HEAD_ONLY = 0.30f; + const float EYE_SEPARATION = 0.1f; + firstEyePosition = baseEyePosition + headRotation * glm::vec3(EYE_SEPARATION, 0.0f, EYES_FORWARD_HEAD_ONLY); + secondEyePosition = baseEyePosition + headRotation * glm::vec3(-EYE_SEPARATION, 0.0f, EYES_FORWARD_HEAD_ONLY); + return true; } return false; } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 262bf2a567..acf5696d65 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -363,13 +363,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); destinationBuffer += sizeof(packetStateFlags); +#define AVATAR_MEMCPY(src) \ + memcpy(destinationBuffer, &(src), sizeof(src)); \ + destinationBuffer += sizeof(src); + if (hasAvatarGlobalPosition) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - data->globalPosition[0] = _globalPosition.x; - data->globalPosition[1] = _globalPosition.y; - data->globalPosition[2] = _globalPosition.z; - destinationBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); + AVATAR_MEMCPY(_globalPosition); int numBytes = destinationBuffer - startSection; @@ -380,17 +380,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasAvatarBoundingBox) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - - data->avatarDimensions[0] = _globalBoundingBoxDimensions.x; - data->avatarDimensions[1] = _globalBoundingBoxDimensions.y; - data->avatarDimensions[2] = _globalBoundingBoxDimensions.z; - - data->boundOriginOffset[0] = _globalBoundingBoxOffset.x; - data->boundOriginOffset[1] = _globalBoundingBoxOffset.y; - data->boundOriginOffset[2] = _globalBoundingBoxOffset.z; - - destinationBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); + AVATAR_MEMCPY(_globalBoundingBoxDimensions); + AVATAR_MEMCPY(_globalBoundingBoxOffset); int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { @@ -424,13 +415,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasLookAtPosition) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - auto lookAt = _headData->getLookAtPosition(); - data->lookAtPosition[0] = lookAt.x; - data->lookAtPosition[1] = lookAt.y; - data->lookAtPosition[2] = lookAt.z; - destinationBuffer += sizeof(AvatarDataPacket::LookAtPosition); - + AVATAR_MEMCPY(_headData->getLookAtPosition()); int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { outboundDataRateOut->lookAtPositionRate.increment(numBytes); @@ -531,12 +516,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasAvatarLocalPosition) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - auto localPosition = getLocalPosition(); - data->localPosition[0] = localPosition.x; - data->localPosition[1] = localPosition.y; - data->localPosition[2] = localPosition.z; - destinationBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition); + const auto localPosition = getLocalPosition(); + AVATAR_MEMCPY(localPosition); int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { @@ -567,19 +548,24 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } + QVector jointData; + if (hasJointData || hasJointDefaultPoseFlags) { + QReadLocker readLock(&_jointDataLock); + jointData = _jointData; + } + // If it is connected, pack up the data if (hasJointData) { auto startSection = destinationBuffer; - QReadLocker readLock(&_jointDataLock); // joint rotation data - int numJoints = _jointData.size(); + int numJoints = jointData.size(); *destinationBuffer++ = (uint8_t)numJoints; unsigned char* validityPosition = destinationBuffer; unsigned char validity = 0; int validityBit = 0; - int numValidityBytes = (int)std::ceil(numJoints / (float)BITS_IN_BYTE); + int numValidityBytes = calcBitVectorSize(numJoints); #ifdef WANT_DEBUG int rotationSentCount = 0; @@ -589,43 +575,37 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += numValidityBytes; // Move pointer past the validity bytes // sentJointDataOut and lastSentJointData might be the same vector - // build sentJointDataOut locally and then swap it at the end. - QVector localSentJointDataOut; if (sentJointDataOut) { - localSentJointDataOut.resize(numJoints); // Make sure the destination is resized before using it + sentJointDataOut->resize(numJoints); // Make sure the destination is resized before using it } - float minRotationDOT = !distanceAdjust ? AVATAR_MIN_ROTATION_DOT : getDistanceBasedMinRotationDOT(viewerPosition); + float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT; - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; + for (int i = 0; i < jointData.size(); i++) { + const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; if (!data.rotationIsDefaultPose) { - bool mustSend = sendAll || last.rotationIsDefaultPose; - if (mustSend || last.rotation != data.rotation) { - - bool largeEnoughRotation = true; - if (cullSmallChanges) { - // The dot product for smaller rotations is a smaller number. - // So if the dot() is less than the value, then the rotation is a larger angle of rotation - largeEnoughRotation = fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT; - } - - if (mustSend || !cullSmallChanges || largeEnoughRotation) { - validity |= (1 << validityBit); + // The dot product for larger rotations is a lower number. + // So if the dot() is less than the value, then the rotation is a larger angle of rotation + if (sendAll || last.rotationIsDefaultPose || (!cullSmallChanges && last.rotation != data.rotation) + || (cullSmallChanges && glm::dot(last.rotation, data.rotation) < minRotationDOT) ) { + validity |= (1 << validityBit); #ifdef WANT_DEBUG - rotationSentCount++; + rotationSentCount++; #endif - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); - if (sentJointDataOut) { - localSentJointDataOut[i].rotation = data.rotation; - localSentJointDataOut[i].rotationIsDefaultPose = false; - } + if (sentJointDataOut) { + (*sentJointDataOut)[i].rotation = data.rotation; } } } + + if (sentJointDataOut) { + (*sentJointDataOut)[i].rotationIsDefaultPose = data.rotationIsDefaultPose; + } + if (++validityBit == BITS_IN_BYTE) { *validityPosition++ = validity; validityBit = validity = 0; @@ -647,35 +627,38 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += numValidityBytes; // Move pointer past the validity bytes - float minTranslation = !distanceAdjust ? AVATAR_MIN_TRANSLATION : getDistanceBasedMinTranslationDistance(viewerPosition); + float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION; float maxTranslationDimension = 0.0; - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; + for (int i = 0; i < jointData.size(); i++) { + const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; if (!data.translationIsDefaultPose) { - bool mustSend = sendAll || last.translationIsDefaultPose; - if (mustSend || last.translation != data.translation) { - if (mustSend || !cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { - validity |= (1 << validityBit); + if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) + || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { + + validity |= (1 << validityBit); #ifdef WANT_DEBUG - translationSentCount++; + translationSentCount++; #endif - maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); - destinationBuffer += - packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + destinationBuffer += + packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); - if (sentJointDataOut) { - localSentJointDataOut[i].translation = data.translation; - localSentJointDataOut[i].translationIsDefaultPose = false; - } + if (sentJointDataOut) { + (*sentJointDataOut)[i].translation = data.translation; } } } + + if (sentJointDataOut) { + (*sentJointDataOut)[i].translationIsDefaultPose = data.translationIsDefaultPose; + } + if (++validityBit == BITS_IN_BYTE) { *validityPosition++ = validity; validityBit = validity = 0; @@ -691,6 +674,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation()); destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(), TRANSLATION_COMPRESSION_RADIX); + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation()); destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), @@ -707,34 +691,27 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent glm::vec3 mouseFarGrabPosition = extractTranslation(mouseFarGrabMatrix); glm::quat mouseFarGrabRotation = extractRotation(mouseFarGrabMatrix); - data->leftFarGrabPosition[0] = leftFarGrabPosition.x; - data->leftFarGrabPosition[1] = leftFarGrabPosition.y; - data->leftFarGrabPosition[2] = leftFarGrabPosition.z; - + AVATAR_MEMCPY(leftFarGrabPosition); + // Can't do block copy as struct order is x, y, z, w. data->leftFarGrabRotation[0] = leftFarGrabRotation.w; data->leftFarGrabRotation[1] = leftFarGrabRotation.x; data->leftFarGrabRotation[2] = leftFarGrabRotation.y; data->leftFarGrabRotation[3] = leftFarGrabRotation.z; + destinationBuffer += sizeof(data->leftFarGrabPosition); - data->rightFarGrabPosition[0] = rightFarGrabPosition.x; - data->rightFarGrabPosition[1] = rightFarGrabPosition.y; - data->rightFarGrabPosition[2] = rightFarGrabPosition.z; - + AVATAR_MEMCPY(rightFarGrabPosition); data->rightFarGrabRotation[0] = rightFarGrabRotation.w; data->rightFarGrabRotation[1] = rightFarGrabRotation.x; data->rightFarGrabRotation[2] = rightFarGrabRotation.y; data->rightFarGrabRotation[3] = rightFarGrabRotation.z; + destinationBuffer += sizeof(data->rightFarGrabRotation); - data->mouseFarGrabPosition[0] = mouseFarGrabPosition.x; - data->mouseFarGrabPosition[1] = mouseFarGrabPosition.y; - data->mouseFarGrabPosition[2] = mouseFarGrabPosition.z; - + AVATAR_MEMCPY(mouseFarGrabPosition); data->mouseFarGrabRotation[0] = mouseFarGrabRotation.w; data->mouseFarGrabRotation[1] = mouseFarGrabRotation.x; data->mouseFarGrabRotation[2] = mouseFarGrabRotation.y; data->mouseFarGrabRotation[3] = mouseFarGrabRotation.z; - - destinationBuffer += sizeof(AvatarDataPacket::FarGrabJoints); + destinationBuffer += sizeof(data->mouseFarGrabRotation); int numBytes = destinationBuffer - startSection; @@ -761,41 +738,23 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent outboundDataRateOut->jointDataRate.increment(numBytes); } - if (sentJointDataOut) { - - // Mark default poses in lastSentJointData, so when they become non-default we send them. - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; - JointData& local = localSentJointDataOut[i]; - if (data.rotationIsDefaultPose) { - local.rotationIsDefaultPose = true; - } - if (data.translationIsDefaultPose) { - local.translationIsDefaultPose = true; - } - } - - // Push new sent joint data to sentJointDataOut - sentJointDataOut->swap(localSentJointDataOut); - } } if (hasJointDefaultPoseFlags) { auto startSection = destinationBuffer; - QReadLocker readLock(&_jointDataLock); // write numJoints - int numJoints = _jointData.size(); + int numJoints = jointData.size(); *destinationBuffer++ = (uint8_t)numJoints; // write rotationIsDefaultPose bits destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { - return _jointData[i].rotationIsDefaultPose; + return jointData[i].rotationIsDefaultPose; }); // write translationIsDefaultPose bits destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { - return _jointData[i].translationIsDefaultPose; + return jointData[i].translationIsDefaultPose; }); if (outboundDataRateOut) { @@ -880,7 +839,6 @@ const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSa // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { - // lazily allocate memory for HeadData in case we're not an Avatar instance lazyInitHeadData(); @@ -918,10 +876,21 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { PACKET_READ_CHECK(AvatarGlobalPosition, sizeof(AvatarDataPacket::AvatarGlobalPosition)); auto data = reinterpret_cast(sourceBuffer); - auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]); + + glm::vec3 offset = glm::vec3(0.0f, 0.0f, 0.0f); + + if (_replicaIndex > 0) { + const float SPACE_BETWEEN_AVATARS = 2.0f; + const int AVATARS_PER_ROW = 3; + int row = _replicaIndex % AVATARS_PER_ROW; + int col = floor(_replicaIndex / AVATARS_PER_ROW); + offset = glm::vec3(row * SPACE_BETWEEN_AVATARS, 0.0f, col * SPACE_BETWEEN_AVATARS); + } + + auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; if (_globalPosition != newValue) { _globalPosition = newValue; - _globalPositionChanged = usecTimestampNow(); + _globalPositionChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); int numBytesRead = sourceBuffer - startSection; @@ -945,11 +914,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if (_globalBoundingBoxDimensions != newDimensions) { _globalBoundingBoxDimensions = newDimensions; - _avatarBoundingBoxChanged = usecTimestampNow(); + _avatarBoundingBoxChanged = now; } if (_globalBoundingBoxOffset != newOffset) { _globalBoundingBoxOffset = newOffset; - _avatarBoundingBoxChanged = usecTimestampNow(); + _avatarBoundingBoxChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); @@ -1050,7 +1019,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans); if (_sensorToWorldMatrixCache.get() != sensorToWorldMatrix) { _sensorToWorldMatrixCache.set(sensorToWorldMatrix); - _sensorToWorldMatrixChanged = usecTimestampNow(); + _sensorToWorldMatrixChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix); int numBytesRead = sourceBuffer - startSection; @@ -1107,7 +1076,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags); if (somethingChanged) { - _additionalFlagsChanged = usecTimestampNow(); + _additionalFlagsChanged = now; } int numBytesRead = sourceBuffer - startSection; _additionalFlagsRate.increment(numBytesRead); @@ -1127,7 +1096,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if ((getParentID() != newParentID) || (getParentJointIndex() != parentInfo->parentJointIndex)) { SpatiallyNestable::setParentID(newParentID); SpatiallyNestable::setParentJointIndex(parentInfo->parentJointIndex); - _parentChanged = usecTimestampNow(); + _parentChanged = now; } int numBytesRead = sourceBuffer - startSection; @@ -1176,8 +1145,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { int numBytesRead = sourceBuffer - startSection; _faceTrackerRate.increment(numBytesRead); _faceTrackerUpdateRate.increment(); - } else { - _headData->_blendshapeCoefficients.fill(0, _headData->_blendshapeCoefficients.size()); } if (hasJointData) { @@ -1884,6 +1851,16 @@ qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTr return bytesWritten; } +void AvatarData::prepareResetTraitInstances() { + if (_clientTraitsHandler) { + _avatarEntitiesLock.withReadLock([this]{ + foreach (auto entityID, _avatarEntityData.keys()) { + _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); + } + }); + } +} + void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) { if (traitType == AvatarTraits::SkeletonModelURL) { // get the URL from the binary data @@ -2781,7 +2758,7 @@ void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { if (_clientTraitsHandler) { // if we have a client traits handler, flag any updated or created entities // so that we send changes for them next frame - foreach (auto entityID, _avatarEntityData) { + foreach (auto entityID, _avatarEntityData.keys()) { _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); } } @@ -2852,10 +2829,10 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra value.extraInfo = object.property("extraInfo").toVariant().toMap(); } -const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f; - -float AvatarData::_avatarSortCoefficientSize { 1.0f }; -float AvatarData::_avatarSortCoefficientCenter { 0.25 }; +// these coefficients can be changed via JS for experimental tuning +// use AvatatManager.setAvatarSortCoefficient("name", value) by a user with domain kick-rights +float AvatarData::_avatarSortCoefficientSize { 8.0f }; +float AvatarData::_avatarSortCoefficientCenter { 0.25f }; float AvatarData::_avatarSortCoefficientAge { 1.0f }; QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 42c2cf22f3..39f0ea34f6 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -337,6 +337,7 @@ enum KillAvatarReason : uint8_t { TheirAvatarEnteredYourBubble, YourAvatarEnteredTheirBubble }; + Q_DECLARE_METATYPE(KillAvatarReason); class QDataStream; @@ -963,6 +964,8 @@ public: qint64 packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID, ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION); + void prepareResetTraitInstances(); + void processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData); void processTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData); @@ -1094,7 +1097,7 @@ public: void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); glm::vec3 getClientGlobalPosition() const { return _globalPosition; } - glm::vec3 getGlobalBoundingBoxCorner() const { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; } + AABox getGlobalBoundingBox() const { return AABox(_globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); } /**jsdoc * @function MyAvatar.getAvatarEntityData @@ -1166,8 +1169,6 @@ public: // A method intended to be overriden by MyAvatar for polling orientation for network transmission. virtual glm::quat getOrientationOutbound() const; - static const float OUT_OF_VIEW_PENALTY; - // TODO: remove this HACK once we settle on optimal sort coefficients // These coefficients exposed for fine tuning the sort priority for transfering new _jointData to the render pipeline. static float _avatarSortCoefficientSize; @@ -1186,6 +1187,8 @@ public: virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {} virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {} + void setReplicaIndex(int replicaIndex) { _replicaIndex = replicaIndex; } + int getReplicaIndex() { return _replicaIndex; } signals: @@ -1435,6 +1438,8 @@ protected: ThreadSafeValueCache _farGrabLeftMatrixCache { glm::mat4() }; ThreadSafeValueCache _farGrabMouseMatrixCache { glm::mat4() }; + ThreadSafeValueCache _collisionCapsuleCache{ QVariantMap() }; + int getFauxJointIndex(const QString& name) const; float _audioLoudness { 0.0f }; @@ -1445,6 +1450,7 @@ protected: udt::SequenceNumber _identitySequenceNumber { 0 }; bool _hasProcessedFirstIdentity { false }; float _density; + int _replicaIndex { 0 }; // null unless MyAvatar or ScriptableAvatar sending traits data to mixer std::unique_ptr _clientTraitsHandler; @@ -1561,7 +1567,7 @@ class RayToAvatarIntersectionResult { public: bool intersects { false }; QUuid avatarID; - float distance { 0.0f }; + float distance { FLT_MAX }; BoxFace face; glm::vec3 intersection; glm::vec3 surfaceNormal; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 64b26131be..01557e307e 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -21,6 +21,101 @@ #include "AvatarLogging.h" #include "AvatarTraits.h" +#include "Profile.h" + +void AvatarReplicas::addReplica(const QUuid& parentID, AvatarSharedPointer replica) { + if (parentID == QUuid()) { + return; + } + if (_replicasMap.find(parentID) == _replicasMap.end()) { + std::vector emptyReplicas = std::vector(); + _replicasMap.insert(std::pair>(parentID, emptyReplicas)); + } + auto &replicas = _replicasMap[parentID]; + replica->setReplicaIndex((int)replicas.size() + 1); + replicas.push_back(replica); +} + +std::vector AvatarReplicas::getReplicaIDs(const QUuid& parentID) { + std::vector ids; + if (_replicasMap.find(parentID) != _replicasMap.end()) { + auto &replicas = _replicasMap[parentID]; + for (int i = 0; i < (int)replicas.size(); i++) { + ids.push_back(replicas[i]->getID()); + } + } else if (_replicaCount > 0) { + for (int i = 0; i < _replicaCount; i++) { + ids.push_back(QUuid::createUuid()); + } + } + return ids; +} + +void AvatarReplicas::parseDataFromBuffer(const QUuid& parentID, const QByteArray& buffer) { + if (_replicasMap.find(parentID) != _replicasMap.end()) { + auto &replicas = _replicasMap[parentID]; + for (auto avatar : replicas) { + avatar->parseDataFromBuffer(buffer); + } + } +} + +void AvatarReplicas::removeReplicas(const QUuid& parentID) { + if (_replicasMap.find(parentID) != _replicasMap.end()) { + _replicasMap.erase(parentID); + } +} + +std::vector AvatarReplicas::takeReplicas(const QUuid& parentID) { + std::vector replicas; + + auto it = _replicasMap.find(parentID); + + if (it != _replicasMap.end()) { + // take a copy of the replica shared pointers for this parent + replicas.swap(it->second); + + // erase the replicas for this parent from our map + _replicasMap.erase(it); + } + + return replicas; +} + +void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) { + if (_replicasMap.find(parentID) != _replicasMap.end()) { + auto &replicas = _replicasMap[parentID]; + for (auto avatar : replicas) { + avatar->processAvatarIdentity(identityData, identityChanged, displayNameChanged); + } + } +} +void AvatarReplicas::processTrait(const QUuid& parentID, AvatarTraits::TraitType traitType, QByteArray traitBinaryData) { + if (_replicasMap.find(parentID) != _replicasMap.end()) { + auto &replicas = _replicasMap[parentID]; + for (auto avatar : replicas) { + avatar->processTrait(traitType, traitBinaryData); + } + } +} +void AvatarReplicas::processDeletedTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID) { + if (_replicasMap.find(parentID) != _replicasMap.end()) { + auto &replicas = _replicasMap[parentID]; + for (auto avatar : replicas) { + avatar->processDeletedTraitInstance(traitType, instanceID); + } + } +} +void AvatarReplicas::processTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) { + if (_replicasMap.find(parentID) != _replicasMap.end()) { + auto &replicas = _replicasMap[parentID]; + for (auto avatar : replicas) { + avatar->processTraitInstance(traitType, instanceID, traitBinaryData); + } + } +} + AvatarHashMap::AvatarHashMap() { auto nodeList = DependencyManager::get(); @@ -31,6 +126,12 @@ AvatarHashMap::AvatarHashMap() { packetReceiver.registerListener(PacketType::BulkAvatarTraits, this, "processBulkAvatarTraits"); connect(nodeList.data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); + + connect(nodeList.data(), &NodeList::nodeKilled, this, [this](SharedNodePointer killedNode){ + if (killedNode->getType() == NodeType::AvatarMixer) { + clearOtherAvatars(); + } + }); } QVector AvatarHashMap::getAvatarIdentifiers() { @@ -64,6 +165,21 @@ bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range return false; } +void AvatarHashMap::setReplicaCount(int count) { + _replicas.setReplicaCount(count); + auto avatars = getAvatarIdentifiers(); + for (int i = 0; i < avatars.size(); i++) { + KillAvatarReason reason = KillAvatarReason::NoReason; + if (avatars[i] != QUuid()) { + removeAvatar(avatars[i], reason); + auto replicaIDs = _replicas.getReplicaIDs(avatars[i]); + for (auto id : replicaIDs) { + removeAvatar(id, reason); + } + } + } +} + int AvatarHashMap::numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters) { auto hashCopy = getHashCopy(); auto rangeMeters2 = rangeMeters * rangeMeters; @@ -118,6 +234,7 @@ AvatarSharedPointer AvatarHashMap::findAvatar(const QUuid& sessionUUID) const { } void AvatarHashMap::processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode) { + DETAILED_PROFILE_RANGE(network, __FUNCTION__); PerformanceTimer perfTimer("receiveAvatar"); // enumerate over all of the avatars in this packet // only add them if mixerWeakPointer points to something (meaning that mixer is still around) @@ -135,18 +252,25 @@ AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer(); - bool isNewAvatar; if (sessionUUID != _lastOwnerSessionUUID && (!nodeList->isIgnoringNode(sessionUUID) || nodeList->getRequestsDomainListData())) { auto avatar = newOrExistingAvatar(sessionUUID, sendingNode, isNewAvatar); + if (isNewAvatar) { QWriteLocker locker(&_hashLock); _pendingAvatars.insert(sessionUUID, { std::chrono::steady_clock::now(), 0, avatar }); - } + auto replicaIDs = _replicas.getReplicaIDs(sessionUUID); + for (auto replicaID : replicaIDs) { + auto replicaAvatar = addAvatar(replicaID, sendingNode); + _replicas.addReplica(sessionUUID, replicaAvatar); + } + } // have the matching (or new) avatar parse the data from the packet int bytesRead = avatar->parseDataFromBuffer(byteArray); message->seek(positionBeforeRead + bytesRead); + _replicas.parseDataFromBuffer(sessionUUID, byteArray); + return avatar; } else { // create a dummy AvatarData class to throw this data on the ground @@ -191,10 +315,13 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer bool displayNameChanged = false; // In this case, the "sendingNode" is the Avatar Mixer. avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); + _replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged); + } } void AvatarHashMap::processBulkAvatarTraits(QSharedPointer message, SharedNodePointer sendingNode) { + while (message->getBytesLeftToRead()) { // read the avatar ID to figure out which avatar this is for auto avatarID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); @@ -202,7 +329,6 @@ void AvatarHashMap::processBulkAvatarTraits(QSharedPointer mess // grab the avatar so we can ask it to process trait data bool isNewAvatar; auto avatar = newOrExistingAvatar(avatarID, sendingNode, isNewAvatar); - // read the first trait type for this avatar AvatarTraits::TraitType traitType; message->readPrimitive(&traitType); @@ -217,13 +343,14 @@ void AvatarHashMap::processBulkAvatarTraits(QSharedPointer mess AvatarTraits::TraitWireSize traitBinarySize; bool skipBinaryTrait = false; - if (AvatarTraits::isSimpleTrait(traitType)) { message->readPrimitive(&traitBinarySize); // check if this trait version is newer than what we already have for this avatar if (packetTraitVersion > lastProcessedVersions[traitType]) { - avatar->processTrait(traitType, message->read(traitBinarySize)); + auto traitData = message->read(traitBinarySize); + avatar->processTrait(traitType, traitData); + _replicas.processTrait(avatarID, traitType, traitData); lastProcessedVersions[traitType] = packetTraitVersion; } else { skipBinaryTrait = true; @@ -238,8 +365,11 @@ void AvatarHashMap::processBulkAvatarTraits(QSharedPointer mess if (packetTraitVersion > processedInstanceVersion) { if (traitBinarySize == AvatarTraits::DELETED_TRAIT_SIZE) { avatar->processDeletedTraitInstance(traitType, traitInstanceID); + _replicas.processDeletedTraitInstance(avatarID, traitType, traitInstanceID); } else { - avatar->processTraitInstance(traitType, traitInstanceID, message->read(traitBinarySize)); + auto traitData = message->read(traitBinarySize); + avatar->processTraitInstance(traitType, traitInstanceID, traitData); + _replicas.processTraitInstance(avatarID, traitType, traitInstanceID, traitData); } processedInstanceVersion = packetTraitVersion; } else { @@ -265,15 +395,36 @@ void AvatarHashMap::processKillAvatar(QSharedPointer message, S KillAvatarReason reason; message->readPrimitive(&reason); removeAvatar(sessionUUID, reason); + auto replicaIDs = _replicas.getReplicaIDs(sessionUUID); + for (auto id : replicaIDs) { + removeAvatar(id, reason); + } } void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) { - QWriteLocker locker(&_hashLock); + std::vector removedAvatars; - _pendingAvatars.remove(sessionUUID); - auto removedAvatar = _avatarHash.take(sessionUUID); + { + QWriteLocker locker(&_hashLock); - if (removedAvatar) { + auto replicas = _replicas.takeReplicas(sessionUUID); + + for (auto& replica : replicas) { + auto removedReplica = _avatarHash.take(replica->getID()); + if (removedReplica) { + removedAvatars.push_back(removedReplica); + } + } + + _pendingAvatars.remove(sessionUUID); + auto removedAvatar = _avatarHash.take(sessionUUID); + + if (removedAvatar) { + removedAvatars.push_back(removedAvatar); + } + } + + for (auto& removedAvatar: removedAvatars) { handleRemovedAvatar(removedAvatar, removalReason); } } @@ -292,3 +443,19 @@ void AvatarHashMap::sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& ol emit avatarSessionChangedEvent(sessionUUID, oldUUID); } +void AvatarHashMap::clearOtherAvatars() { + QList removedAvatars; + + { + QWriteLocker locker(&_hashLock); + + // grab a copy of the current avatars so we can call handleRemoveAvatar for them + removedAvatars = _avatarHash.values(); + + _avatarHash.clear(); + } + + for (auto& av : removedAvatars) { + handleRemovedAvatar(av); + } +} diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index ba16fa9568..c2cb448e52 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -41,6 +41,28 @@ * @hifi-assignment-client */ +class AvatarReplicas { +public: + AvatarReplicas() {} + void addReplica(const QUuid& parentID, AvatarSharedPointer replica); + std::vector getReplicaIDs(const QUuid& parentID); + void parseDataFromBuffer(const QUuid& parentID, const QByteArray& buffer); + void processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged); + void removeReplicas(const QUuid& parentID); + std::vector takeReplicas(const QUuid& parentID); + void processTrait(const QUuid& parentID, AvatarTraits::TraitType traitType, QByteArray traitBinaryData); + void processDeletedTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID); + void processTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData); + void setReplicaCount(int count) { _replicaCount = count; } + int getReplicaCount() { return _replicaCount; } + +private: + std::map> _replicasMap; + int _replicaCount { 0 }; +}; + + class AvatarHashMap : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -77,6 +99,11 @@ public: virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) const { return findAvatar(sessionID); } int numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters); + void setReplicaCount(int count); + int getReplicaCount() { return _replicas.getReplicaCount(); }; + + virtual void clearOtherAvatars(); + signals: /**jsdoc @@ -153,7 +180,7 @@ protected: bool& isNew); virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason); - + virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason); AvatarHash _avatarHash; @@ -167,6 +194,8 @@ protected: mutable QReadWriteLock _hashLock; std::unordered_map _processedTraitVersions; + AvatarReplicas _replicas; + private: QUuid _lastOwnerSessionUUID; }; diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index c4073cb86a..a06b53da7c 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -37,6 +37,12 @@ void ClientTraitsHandler::resetForNewMixer() { // mark that all traits should be sent next time _shouldPerformInitialSend = true; + + // reset the trait statuses + _traitStatuses.reset(); + + // pre-fill the instanced statuses that we will need to send next frame + _owningAvatar->prepareResetTraitInstances(); } void ClientTraitsHandler::sendChangedTraitsToMixer() { diff --git a/libraries/controllers/src/controllers/Input.h b/libraries/controllers/src/controllers/Input.h index 3ca4076de2..3c01ee0942 100644 --- a/libraries/controllers/src/controllers/Input.h +++ b/libraries/controllers/src/controllers/Input.h @@ -19,16 +19,16 @@ struct InputCalibrationData { glm::mat4 sensorToWorldMat; // sensor to world glm::mat4 avatarMat; // avatar to world glm::mat4 hmdSensorMat; // hmd pos and orientation in sensor space - glm::mat4 defaultCenterEyeMat; // default pose for the center of the eyes in avatar space. - glm::mat4 defaultHeadMat; // default pose for head joint in avatar space - glm::mat4 defaultSpine2; // default pose for spine2 joint in avatar space - glm::mat4 defaultHips; // default pose for hips joint in avatar space - glm::mat4 defaultLeftFoot; // default pose for leftFoot joint in avatar space - glm::mat4 defaultRightFoot; // default pose for rightFoot joint in avatar space - glm::mat4 defaultRightArm; // default pose for rightArm joint in avatar space - glm::mat4 defaultLeftArm; // default pose for leftArm joint in avatar space - glm::mat4 defaultRightHand; // default pose for rightHand joint in avatar space - glm::mat4 defaultLeftHand; // default pose for leftHand joint in avatar space + glm::mat4 defaultCenterEyeMat; // default pose for the center of the eyes in sensor space. + glm::mat4 defaultHeadMat; // default pose for head joint in sensor space + glm::mat4 defaultSpine2; // default pose for spine2 joint in sensor space + glm::mat4 defaultHips; // default pose for hips joint in sensor space + glm::mat4 defaultLeftFoot; // default pose for leftFoot joint in sensor space + glm::mat4 defaultRightFoot; // default pose for rightFoot joint in sensor space + glm::mat4 defaultRightArm; // default pose for rightArm joint in sensor space + glm::mat4 defaultLeftArm; // default pose for leftArm joint in sensor space + glm::mat4 defaultRightHand; // default pose for rightHand joint in sensor space + glm::mat4 defaultLeftHand; // default pose for leftHand joint in sensor space }; enum class ChannelType { diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 371deec7d5..307064c073 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -527,8 +527,8 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { } // If the source hasn't been written yet, defer processing of this route - auto source = route->source; - auto sourceInput = source->getInput(); + auto& source = route->source; + auto& sourceInput = source->getInput(); if (sourceInput.device == STANDARD_DEVICE && !force && source->writeable()) { if (debugRoutes && route->debug) { qCDebug(controllers) << "Source not yet written, deferring"; @@ -559,7 +559,7 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { return true; } - auto destination = route->destination; + auto& destination = route->destination; // THis could happen if the route destination failed to create // FIXME: Maybe do not create the route if the destination failed and avoid this case ? if (!destination) { diff --git a/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf b/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf index 5f7b3f3411..17dedce7f9 100644 --- a/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf +++ b/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf @@ -11,7 +11,7 @@ layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; void main(void) { - ivec2 texCoord = ivec2(floor(varTexCoord0 * textureData.textureSize)); + ivec2 texCoord = ivec2(floor(varTexCoord0 * vec2(textureData.textureSize))); texCoord.x /= 2; int row = int(floor(gl_FragCoord.y)); if (row % 2 > 0) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c3c4095251..3d782f69a7 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -382,6 +382,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene const auto& views = _viewState->getConicalViews(); PrioritySortUtil::PriorityQueue sortedRenderables(views); + sortedRenderables.reserve(_renderablesToUpdate.size()); { PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); @@ -405,11 +406,14 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // process the sorted renderables size_t numSorted = sortedRenderables.size(); - while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const auto renderable = sortedRenderables.top().getRenderer(); + const auto& sortedRenderablesVector = sortedRenderables.getSortedVector(); + for (const auto& sortedRenderable : sortedRenderablesVector) { + if (usecTimestampNow() > expiry) { + break; + } + const auto& renderable = sortedRenderable.getRenderer(); renderable->updateInScene(scene, transaction); _renderablesToUpdate.erase(renderable->getEntity()->getID()); - sortedRenderables.pop(); } // compute average per-renderable update cost diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 34936c2c48..3a01650a04 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1297,9 +1297,20 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } }); - // Check for removal ModelPointer model; withReadLock([&] { model = _model; }); + + withWriteLock([&] { + bool visuallyReady = true; + if (_hasModel) { + if (model && _didLastVisualGeometryRequestSucceed) { + visuallyReady = (_prevModelLoaded && _texturesLoaded); + } + } + entity->setVisuallyReady(visuallyReady); + }); + + // Check for removal if (!_hasModel) { if (model) { model->removeFromScene(scene, transaction); @@ -1311,6 +1322,8 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce emit DependencyManager::get()-> modelRemovedFromScene(entity->getEntityItemID(), NestableType::Entity, _model); } + setKey(false); + _didLastVisualGeometryRequestSucceed = false; return; } @@ -1336,6 +1349,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce if (_parsedModelURL != model->getURL()) { withWriteLock([&] { _texturesLoaded = false; + _jointMappingCompleted = false; model->setURL(_parsedModelURL); }); } @@ -1441,12 +1455,11 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // That is where _currentFrame and _lastAnimated were updated. if (_animating) { DETAILED_PROFILE_RANGE(simulation_physics, "Animate"); - + if (!jointsMapped()) { mapJoints(entity, model->getJointNames()); //else the joint have been mapped before but we have a new animation to load - } else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) { - _animation = DependencyManager::get()->getAnimation(entity->getAnimationURL()); + } else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) { _jointMappingCompleted = false; mapJoints(entity, model->getJointNames()); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 45892fdd7f..75f35fae9c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -187,7 +187,7 @@ private: const void* _collisionMeshKey { nullptr }; // used on client side - bool _jointMappingCompleted{ false }; + bool _jointMappingCompleted { false }; QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints AnimationPointer _animation; QUrl _parsedModelURL; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 18c4921836..1a263fba79 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -187,12 +187,10 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa particle.basePosition = baseTransform.getTranslation(); // Position, velocity, and acceleration + glm::vec3 emitDirection; if (polarStart == 0.0f && polarFinish == 0.0f && emitDimensions.z == 0.0f) { // Emit along z-axis from position - - particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * Vectors::UNIT_Z); - particle.acceleration = emitAcceleration + randFloatInRange(-1.0f, 1.0f) * accelerationSpread; - + emitDirection = Vectors::UNIT_Z; } else { // Emit around point or from ellipsoid // - Distribute directions evenly around point @@ -210,7 +208,6 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat(); } - glm::vec3 emitDirection; if (emitDimensions == Vectors::ZERO) { // Point emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Vectors::UNIT_Z; @@ -235,10 +232,10 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa )); particle.relativePosition += emitOrientation * emitPosition; } - - particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * emitDirection); - particle.acceleration = emitAcceleration + randFloatInRange(-1.0f, 1.0f) * accelerationSpread; } + particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * emitDirection); + particle.acceleration = emitAcceleration + + glm::vec3(randFloatInRange(-1.0f, 1.0f), randFloatInRange(-1.0f, 1.0f), randFloatInRange(-1.0f, 1.0f)) * accelerationSpread; return particle; } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index c11ccb70a0..a4a5a34683 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -571,7 +571,6 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o } glm::mat4 wtvMatrix = worldToVoxelMatrix(); - glm::mat4 vtwMatrix = voxelToWorldMatrix(); glm::vec3 normDirection = glm::normalize(direction); // the PolyVox ray intersection code requires a near and far point. @@ -584,8 +583,6 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f); glm::vec4 farInVoxel = wtvMatrix * glm::vec4(farPoint, 1.0f); - glm::vec4 directionInVoxel = glm::normalize(farInVoxel - originInVoxel); - glm::vec4 result = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); PolyVox::RaycastResult raycastResult = doRayCast(originInVoxel, farInVoxel, result); if (raycastResult == PolyVox::RaycastResults::Completed) { @@ -599,14 +596,9 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o voxelBox += result3 - Vectors::HALF; voxelBox += result3 + Vectors::HALF; - float voxelDistance; - bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), - voxelDistance, face, surfaceNormal); - - glm::vec4 voxelIntersectionPoint = glm::vec4(glm::vec3(originInVoxel) + glm::vec3(directionInVoxel) * voxelDistance, 1.0); - glm::vec4 intersectionPoint = vtwMatrix * voxelIntersectionPoint; - distance = glm::distance(origin, glm::vec3(intersectionPoint)); - return hit; + glm::vec3 directionInVoxel = vec3(wtvMatrix * glm::vec4(direction, 0.0f)); + return voxelBox.findRayIntersection(glm::vec3(originInVoxel), directionInVoxel, 1.0f / directionInVoxel, + distance, face, surfaceNormal); } bool RenderablePolyVoxEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, @@ -1617,6 +1609,7 @@ PolyVoxEntityRenderer::PolyVoxEntityRenderer(const EntityItemPointer& entity) : _vertexFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); _vertexFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 12); }); + _params = std::make_shared(sizeof(glm::vec4), nullptr); } ShapeKey PolyVoxEntityRenderer::getShapeKey() { @@ -1679,9 +1672,12 @@ void PolyVoxEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& s void PolyVoxEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { _lastVoxelToWorldMatrix = entity->voxelToWorldMatrix(); + _lastVoxelVolumeSize = entity->getVoxelVolumeSize(); + _params->setSubData(0, vec4(_lastVoxelVolumeSize, 0.0)); graphics::MeshPointer newMesh; entity->withReadLock([&] { newMesh = entity->_mesh; + }); if (newMesh && newMesh->getIndexBuffer()._buffer) { @@ -1694,6 +1690,7 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) { return; } + PerformanceTimer perfTimer("RenderablePolyVoxEntityItem::render"); gpu::Batch& batch = *args->_batch; @@ -1703,6 +1700,7 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) { batch.setInputBuffer(gpu::Stream::POSITION, _mesh->getVertexBuffer()._buffer, 0, sizeof(PolyVox::PositionMaterialNormal)); + // TODO -- should we be setting this? // batch.setInputBuffer(gpu::Stream::NORMAL, mesh->getVertexBuffer()._buffer, // 12, @@ -1718,7 +1716,7 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) { } } - batch._glUniform3f(entities_renderer::slot::uniform::PolyvoxVoxelSize, _lastVoxelVolumeSize.x, _lastVoxelVolumeSize.y, _lastVoxelVolumeSize.z); + batch.setUniformBuffer(0, _params); batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)_mesh->getNumIndices(), 0); } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 7afb9b41b4..366a3fdc70 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -187,6 +187,7 @@ private: #endif graphics::MeshPointer _mesh; + gpu::BufferPointer _params; std::array _xyzTextures; glm::vec3 _lastVoxelVolumeSize; glm::mat4 _lastVoxelToWorldMatrix; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 9430e97e54..cf452c9cf7 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -288,6 +288,17 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen updateHazeFromEntity(entity); } + + bool visuallyReady = true; + uint32_t skyboxMode = entity->getSkyboxMode(); + if (skyboxMode == COMPONENT_MODE_ENABLED && !_skyboxTextureURL.isEmpty()) { + bool skyboxLoadedOrFailed = (_skyboxTexture && (_skyboxTexture->isLoaded() || _skyboxTexture->isFailed())); + + visuallyReady = skyboxLoadedOrFailed; + } + + entity->setVisuallyReady(visuallyReady); + if (bloomChanged) { updateBloomFromEntity(entity); } diff --git a/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h b/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h index 58b0bf5688..d01075846a 100644 --- a/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h +++ b/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h @@ -15,7 +15,6 @@ #define ENTITIES_SHADER_CONSTANTS_H // Polyvox -#define ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE 0 #define ENTITIES_TEXTURE_POLYVOX_XMAP 0 #define ENTITIES_TEXTURE_POLYVOX_YMAP 1 #define ENTITIES_TEXTURE_POLYVOX_ZMAP 2 @@ -26,17 +25,6 @@ namespace entities_renderer { namespace slot { -namespace uniform { -enum Uniform { - PolyvoxVoxelSize = ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE, -}; -} - -namespace buffer { -enum Buffer { -}; -} // namespace buffer - namespace texture { enum Texture { PolyvoxXMap = ENTITIES_TEXTURE_POLYVOX_XMAP, diff --git a/libraries/entities-renderer/src/paintStroke_fade.slf b/libraries/entities-renderer/src/paintStroke_fade.slf index 8739c9bb9b..e5f70c8038 100644 --- a/libraries/entities-renderer/src/paintStroke_fade.slf +++ b/libraries/entities-renderer/src/paintStroke_fade.slf @@ -45,7 +45,7 @@ void main(void) { int frontCondition = 1 -int(gl_FrontFacing) * 2; vec3 color = varColor.rgb; packDeferredFragmentTranslucent( - interpolatedNormal * frontCondition, + interpolatedNormal * float(frontCondition), texel.a * varColor.a, polyline.color * texel.rgb + fadeEmissive, vec3(0.01, 0.01, 0.01), diff --git a/libraries/entities-renderer/src/polyvox.slf b/libraries/entities-renderer/src/polyvox.slf index ba2fd7031b..441dfc11e5 100644 --- a/libraries/entities-renderer/src/polyvox.slf +++ b/libraries/entities-renderer/src/polyvox.slf @@ -23,15 +23,22 @@ layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _worldPosition; layout(binding=ENTITIES_TEXTURE_POLYVOX_XMAP) uniform sampler2D xMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_YMAP) uniform sampler2D yMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_ZMAP) uniform sampler2D zMap; -layout(location=ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE) uniform vec3 voxelVolumeSize; + +struct PolyvoxParams { + vec4 voxelVolumeSize; +}; + +layout(binding=0) uniform polyvoxParamsBuffer { + PolyvoxParams params; +}; void main(void) { vec3 worldNormal = cross(dFdy(_worldPosition.xyz), dFdx(_worldPosition.xyz)); worldNormal = normalize(worldNormal); - float inPositionX = (_worldPosition.x - 0.5) / voxelVolumeSize.x; - float inPositionY = (_worldPosition.y - 0.5) / voxelVolumeSize.y; - float inPositionZ = (_worldPosition.z - 0.5) / voxelVolumeSize.z; + float inPositionX = (_worldPosition.x - 0.5) / params.voxelVolumeSize.x; + float inPositionY = (_worldPosition.y - 0.5) / params.voxelVolumeSize.y; + float inPositionZ = (_worldPosition.z - 0.5) / params.voxelVolumeSize.z; vec4 xyDiffuse = texture(xMap, vec2(-inPositionX, -inPositionY)); vec4 xzDiffuse = texture(yMap, vec2(-inPositionX, inPositionZ)); diff --git a/libraries/entities-renderer/src/polyvox_fade.slf b/libraries/entities-renderer/src/polyvox_fade.slf index 2247e472ea..6e236193aa 100644 --- a/libraries/entities-renderer/src/polyvox_fade.slf +++ b/libraries/entities-renderer/src/polyvox_fade.slf @@ -27,7 +27,13 @@ layout(binding=ENTITIES_TEXTURE_POLYVOX_XMAP) uniform sampler2D xMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_YMAP) uniform sampler2D yMap; layout(binding=ENTITIES_TEXTURE_POLYVOX_ZMAP) uniform sampler2D zMap; -layout(location=ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE) uniform vec3 voxelVolumeSize; +struct PolyvoxParams { + vec4 voxelVolumeSize; +}; + +layout(binding=0) uniform polyvoxParamsBuffer { + PolyvoxParams params; +}; // Declare after all samplers to prevent sampler location mix up with voxel shading (sampler locations are hardcoded in RenderablePolyVoxEntityItem) <$declareFadeFragment()$> @@ -42,9 +48,9 @@ void main(void) { vec3 worldNormal = cross(dFdy(_worldPosition.xyz), dFdx(_worldPosition.xyz)); worldNormal = normalize(worldNormal); - float inPositionX = (_worldPosition.x - 0.5) / voxelVolumeSize.x; - float inPositionY = (_worldPosition.y - 0.5) / voxelVolumeSize.y; - float inPositionZ = (_worldPosition.z - 0.5) / voxelVolumeSize.z; + float inPositionX = (_worldPosition.x - 0.5) / params.voxelVolumeSize.x; + float inPositionY = (_worldPosition.y - 0.5) / params.voxelVolumeSize.y; + float inPositionZ = (_worldPosition.z - 0.5) / params.voxelVolumeSize.z; vec4 xyDiffuse = texture(xMap, vec2(-inPositionX, -inPositionY)); vec4 xzDiffuse = texture(yMap, vec2(-inPositionX, inPositionZ)); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 47ae8de9ad..490f9b9e6b 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -305,6 +305,7 @@ public: void setDynamic(bool value); virtual bool shouldBePhysical() const { return false; } + bool isVisuallyReady() const { return _visuallyReady; } bool getLocked() const; void setLocked(bool value); @@ -527,6 +528,7 @@ public: void removeCloneID(const QUuid& cloneID); const QVector getCloneIDs() const; void setCloneIDs(const QVector& cloneIDs); + void setVisuallyReady(bool visuallyReady) { _visuallyReady = visuallyReady; } signals: void requestRenderUpdate(); @@ -639,6 +641,7 @@ protected: EntityTreeElementPointer _element; // set by EntityTreeElement void* _physicsInfo { nullptr }; // set by EntitySimulation bool _simulated { false }; // set by EntitySimulation + bool _visuallyReady { true }; bool addActionInternal(EntitySimulationPointer simulation, EntityDynamicPointer action); bool removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation = nullptr); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index d9924cb9fd..8f0fde5c9a 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -132,8 +132,8 @@ EntityItemProperties convertPropertiesToScriptSemantics(const EntityItemProperti EntityItemProperties scriptSideProperties = entitySideProperties; scriptSideProperties.setLocalPosition(entitySideProperties.getPosition()); scriptSideProperties.setLocalRotation(entitySideProperties.getRotation()); - scriptSideProperties.setLocalVelocity(entitySideProperties.getLocalVelocity()); - scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getLocalAngularVelocity()); + scriptSideProperties.setLocalVelocity(entitySideProperties.getVelocity()); + scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getAngularVelocity()); scriptSideProperties.setLocalDimensions(entitySideProperties.getDimensions()); bool success; @@ -181,8 +181,6 @@ EntityItemProperties convertPropertiesFromScriptSemantics(const EntityItemProper EntityItemProperties entitySideProperties = scriptSideProperties; bool success; - // TODO -- handle velocity and angularVelocity - if (scriptSideProperties.localPositionChanged()) { entitySideProperties.setPosition(scriptSideProperties.getLocalPosition()); } else if (scriptSideProperties.positionChanged()) { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 377e192bb1..a7c88ddc7d 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -48,6 +48,7 @@ public: // Inputs glm::vec3 origin; glm::vec3 direction; + glm::vec3 invDirection; const QVector& entityIdsToInclude; const QVector& entityIdsToDiscard; bool visibleOnly; @@ -825,28 +826,51 @@ bool findRayIntersectionOp(const OctreeElementPointer& element, void* extraData) RayArgs* args = static_cast(extraData); bool keepSearching = true; EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast(element); - EntityItemID entityID = entityTreeElementPointer->findRayIntersection(args->origin, args->direction, keepSearching, + EntityItemID entityID = entityTreeElementPointer->findRayIntersection(args->origin, args->direction, args->element, args->distance, args->face, args->surfaceNormal, args->entityIdsToInclude, args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking); if (!entityID.isNull()) { args->entityID = entityID; + // We recurse OctreeElements in order, so if we hit something, we can stop immediately + keepSearching = false; } return keepSearching; } +float findRayIntersectionSortingOp(const OctreeElementPointer& element, void* extraData) { + RayArgs* args = static_cast(extraData); + EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast(element); + float distance = FLT_MAX; + // If origin is inside the cube, always check this element first + if (entityTreeElementPointer->getAACube().contains(args->origin)) { + distance = 0.0f; + } else { + float boundDistance = FLT_MAX; + BoxFace face; + glm::vec3 surfaceNormal; + if (entityTreeElementPointer->getAACube().findRayIntersection(args->origin, args->direction, args->invDirection, boundDistance, face, surfaceNormal)) { + // Don't add this cell if it's already farther than our best distance so far + if (boundDistance < args->distance) { + distance = boundDistance; + } + } + } + return distance; +} + EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector entityIdsToInclude, QVector entityIdsToDiscard, bool visibleOnly, bool collidableOnly, bool precisionPicking, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType, bool* accurateResult) { - RayArgs args = { origin, direction, entityIdsToInclude, entityIdsToDiscard, + RayArgs args = { origin, direction, 1.0f / direction, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, precisionPicking, element, distance, face, surfaceNormal, extraInfo, EntityItemID() }; distance = FLT_MAX; bool requireLock = lockType == Octree::Lock; bool lockResult = withReadLock([&]{ - recurseTreeWithOperation(findRayIntersectionOp, &args); + recurseTreeWithOperationSorted(findRayIntersectionOp, findRayIntersectionSortingOp, &args); }, requireLock); if (accurateResult) { @@ -860,15 +884,38 @@ bool findParabolaIntersectionOp(const OctreeElementPointer& element, void* extra ParabolaArgs* args = static_cast(extraData); bool keepSearching = true; EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast(element); - EntityItemID entityID = entityTreeElementPointer->findParabolaIntersection(args->origin, args->velocity, args->acceleration, keepSearching, + EntityItemID entityID = entityTreeElementPointer->findParabolaIntersection(args->origin, args->velocity, args->acceleration, args->element, args->parabolicDistance, args->face, args->surfaceNormal, args->entityIdsToInclude, args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking); if (!entityID.isNull()) { args->entityID = entityID; + // We recurse OctreeElements in order, so if we hit something, we can stop immediately + keepSearching = false; } return keepSearching; } +float findParabolaIntersectionSortingOp(const OctreeElementPointer& element, void* extraData) { + ParabolaArgs* args = static_cast(extraData); + EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast(element); + float distance = FLT_MAX; + // If origin is inside the cube, always check this element first + if (entityTreeElementPointer->getAACube().contains(args->origin)) { + distance = 0.0f; + } else { + float boundDistance = FLT_MAX; + BoxFace face; + glm::vec3 surfaceNormal; + if (entityTreeElementPointer->getAACube().findParabolaIntersection(args->origin, args->velocity, args->acceleration, boundDistance, face, surfaceNormal)) { + // Don't add this cell if it's already farther than our best distance so far + if (boundDistance < args->parabolicDistance) { + distance = boundDistance; + } + } + } + return distance; +} + EntityItemID EntityTree::findParabolaIntersection(const PickParabola& parabola, QVector entityIdsToInclude, QVector entityIdsToDiscard, bool visibleOnly, bool collidableOnly, bool precisionPicking, @@ -882,7 +929,7 @@ EntityItemID EntityTree::findParabolaIntersection(const PickParabola& parabola, bool requireLock = lockType == Octree::Lock; bool lockResult = withReadLock([&] { - recurseTreeWithOperation(findParabolaIntersectionOp, &args); + recurseTreeWithOperationSorted(findParabolaIntersectionOp, findParabolaIntersectionSortingOp, &args); }, requireLock); if (accurateResult) { diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 5974fce6c5..efe5dafccf 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -140,31 +140,18 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3 } EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, - const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, - QVariantMap& extraInfo, bool precisionPicking) { + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, + bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) { EntityItemID result; - float distanceToElementCube = std::numeric_limits::max(); BoxFace localFace; glm::vec3 localSurfaceNormal; - // if the ray doesn't intersect with our cube OR the distance to element is less than current best distance - // we can stop searching! - bool hit = _cube.findRayIntersection(origin, direction, distanceToElementCube, localFace, localSurfaceNormal); - if (!hit || (!_cube.contains(origin) && distanceToElementCube > distance)) { - keepSearching = false; // no point in continuing to search - return result; // we did not intersect - } - - // by default, we only allow intersections with leaves with content if (!canPickIntersect()) { - return result; // we don't intersect with non-leaves, and we keep searching + return result; } - // if the distance to the element cube is not less than the current best distance, then it's not possible - // for any details inside the cube to be closer so we don't need to consider them. QVariantMap localExtraInfo; float distanceToElementDetails = distance; EntityItemID entityID = findDetailedRayIntersection(origin, direction, element, distanceToElementDetails, @@ -228,7 +215,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori float localDistance; BoxFace localFace; glm::vec3 localSurfaceNormal; - if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance, + if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, 1.0f / entityFrameDirection, localDistance, localFace, localSurfaceNormal)) { if (entityFrameBox.contains(entityFrameOrigin) || localDistance < distance) { // now ask the entity if we actually intersect @@ -289,31 +276,19 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad } EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, bool& keepSearching, OctreeElementPointer& element, float& parabolicDistance, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) { EntityItemID result; - float distanceToElementCube = std::numeric_limits::max(); BoxFace localFace; glm::vec3 localSurfaceNormal; - // if the parabola doesn't intersect with our cube OR the distance to element is less than current best distance - // we can stop searching! - bool hit = _cube.findParabolaIntersection(origin, velocity, acceleration, distanceToElementCube, localFace, localSurfaceNormal); - if (!hit || (!_cube.contains(origin) && distanceToElementCube > parabolicDistance)) { - keepSearching = false; // no point in continuing to search - return result; // we did not intersect - } - - // by default, we only allow intersections with leaves with content if (!canPickIntersect()) { - return result; // we don't intersect with non-leaves, and we keep searching + return result; } - // if the distance to the element cube is not less than the current best distance, then it's not possible - // for any details inside the cube to be closer so we don't need to consider them. QVariantMap localExtraInfo; float distanceToElementDetails = parabolicDistance; // We can precompute the world-space parabola normal and reuse it for the parabola plane intersects AABox sphere check diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index d6f9db08d6..793340c9a4 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -136,10 +136,9 @@ public: virtual bool canPickIntersect() const override { return hasEntities(); } virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, - const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, - QVariantMap& extraInfo, bool precisionPicking = false); + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, + bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking = false); virtual EntityItemID findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, @@ -149,7 +148,7 @@ public: glm::vec3& penetration, void** penetratedObject) const override; virtual EntityItemID findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, - const glm::vec3& acceleration, bool& keepSearching, OctreeElementPointer& element, float& parabolicDistance, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking = false); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 5d5344c9c8..f006692415 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -40,6 +40,7 @@ ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID) : EntityItem( _type = EntityTypes::Model; _lastKnownCurrentFrame = -1; _color[0] = _color[1] = _color[2] = 0; + _visuallyReady = false; } const QString ModelEntityItem::getTextures() const { @@ -65,7 +66,9 @@ EntityItemProperties ModelEntityItem::getProperties(EntityPropertyFlags desiredP COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslationsSet, getJointTranslationsSet); COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslations, getJointTranslations); COPY_ENTITY_PROPERTY_TO_PROPERTIES(relayParentJoints, getRelayParentJoints); - _animationProperties.getProperties(properties); + withReadLock([&] { + _animationProperties.getProperties(properties); + }); return properties; } @@ -122,15 +125,18 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, // grab a local copy of _animationProperties to avoid multiple locks int bytesFromAnimation; + AnimationPropertyGroup animationProperties; withReadLock([&] { - AnimationPropertyGroup animationProperties = _animationProperties; + animationProperties = _animationProperties; bytesFromAnimation = animationProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, animationPropertiesChanged); - if (animationPropertiesChanged) { - applyNewAnimationProperties(animationProperties); - somethingChanged = true; - } }); + if (animationPropertiesChanged) { + withWriteLock([&] { + applyNewAnimationProperties(animationProperties); + }); + somethingChanged = true; + } bytesRead += bytesFromAnimation; dataAt += bytesFromAnimation; @@ -304,69 +310,77 @@ void ModelEntityItem::setAnimationURL(const QString& url) { void ModelEntityItem::setAnimationSettings(const QString& value) { // NOTE: this method only called for old bitstream format + AnimationPropertyGroup animationProperties; + withReadLock([&] { + animationProperties = _animationProperties; + }); + + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, currentFrame, or running, those values will be parsed out and + // will over ride the regular animation settings + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + if (settingsMap.contains("fps")) { + float fps = settingsMap["fps"].toFloat(); + animationProperties.setFPS(fps); + } + + // old settings used frameIndex + if (settingsMap.contains("frameIndex")) { + float currentFrame = settingsMap["frameIndex"].toFloat(); + animationProperties.setCurrentFrame(currentFrame); + } + + if (settingsMap.contains("running")) { + bool running = settingsMap["running"].toBool(); + if (running != animationProperties.getRunning()) { + animationProperties.setRunning(running); + } + } + + if (settingsMap.contains("firstFrame")) { + float firstFrame = settingsMap["firstFrame"].toFloat(); + animationProperties.setFirstFrame(firstFrame); + } + + if (settingsMap.contains("lastFrame")) { + float lastFrame = settingsMap["lastFrame"].toFloat(); + animationProperties.setLastFrame(lastFrame); + } + + if (settingsMap.contains("loop")) { + bool loop = settingsMap["loop"].toBool(); + animationProperties.setLoop(loop); + } + + if (settingsMap.contains("hold")) { + bool hold = settingsMap["hold"].toBool(); + animationProperties.setHold(hold); + } + + if (settingsMap.contains("allowTranslation")) { + bool allowTranslation = settingsMap["allowTranslation"].toBool(); + animationProperties.setAllowTranslation(allowTranslation); + } + withWriteLock([&] { - auto animationProperties = _animationProperties; - - // the animations setting is a JSON string that may contain various animation settings. - // if it includes fps, currentFrame, or running, those values will be parsed out and - // will over ride the regular animation settings - QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); - QJsonObject settingsAsJsonObject = settingsAsJson.object(); - QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); - if (settingsMap.contains("fps")) { - float fps = settingsMap["fps"].toFloat(); - animationProperties.setFPS(fps); - } - - // old settings used frameIndex - if (settingsMap.contains("frameIndex")) { - float currentFrame = settingsMap["frameIndex"].toFloat(); - animationProperties.setCurrentFrame(currentFrame); - } - - if (settingsMap.contains("running")) { - bool running = settingsMap["running"].toBool(); - if (running != animationProperties.getRunning()) { - animationProperties.setRunning(running); - } - } - - if (settingsMap.contains("firstFrame")) { - float firstFrame = settingsMap["firstFrame"].toFloat(); - animationProperties.setFirstFrame(firstFrame); - } - - if (settingsMap.contains("lastFrame")) { - float lastFrame = settingsMap["lastFrame"].toFloat(); - animationProperties.setLastFrame(lastFrame); - } - - if (settingsMap.contains("loop")) { - bool loop = settingsMap["loop"].toBool(); - animationProperties.setLoop(loop); - } - - if (settingsMap.contains("hold")) { - bool hold = settingsMap["hold"].toBool(); - animationProperties.setHold(hold); - } - - if (settingsMap.contains("allowTranslation")) { - bool allowTranslation = settingsMap["allowTranslation"].toBool(); - animationProperties.setAllowTranslation(allowTranslation); - } applyNewAnimationProperties(animationProperties); }); } void ModelEntityItem::setAnimationIsPlaying(bool value) { _flags |= Simulation::DIRTY_UPDATEABLE; - _animationProperties.setRunning(value); + withWriteLock([&] { + _animationProperties.setRunning(value); + }); } void ModelEntityItem::setAnimationFPS(float value) { _flags |= Simulation::DIRTY_UPDATEABLE; - _animationProperties.setFPS(value); + withWriteLock([&] { + _animationProperties.setFPS(value); + }); } // virtual @@ -556,11 +570,9 @@ void ModelEntityItem::setColor(const xColor& value) { // Animation related items... AnimationPropertyGroup ModelEntityItem::getAnimationProperties() const { - AnimationPropertyGroup result; - withReadLock([&] { - result = _animationProperties; + return resultWithReadLock([&] { + return _animationProperties; }); - return result; } bool ModelEntityItem::hasAnimation() const { @@ -581,6 +593,18 @@ void ModelEntityItem::setAnimationCurrentFrame(float value) { }); } +void ModelEntityItem::setAnimationAllowTranslation(bool value) { + withWriteLock([&] { + _animationProperties.setAllowTranslation(value); + }); +} + +bool ModelEntityItem::getAnimationAllowTranslation() const { + return resultWithReadLock([&] { + return _animationProperties.getAllowTranslation(); + }); +} + void ModelEntityItem::setAnimationLoop(bool loop) { withWriteLock([&] { _animationProperties.setLoop(loop); diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index ad6cdf4040..ef758b9dde 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -88,8 +88,8 @@ public: void setAnimationIsPlaying(bool value); void setAnimationFPS(float value); - void setAnimationAllowTranslation(bool value) { _animationProperties.setAllowTranslation(value); }; - bool getAnimationAllowTranslation() const { return _animationProperties.getAllowTranslation(); }; + void setAnimationAllowTranslation(bool value); + bool getAnimationAllowTranslation() const; void setAnimationLoop(bool loop); bool getAnimationLoop() const; diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index e4ea1470c1..773a7059dc 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -262,20 +262,18 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix(); glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); - glm::vec3 entityFrameDirection = glm::normalize(glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f))); + glm::vec3 entityFrameDirection = glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f)); - float localDistance; // NOTE: unit sphere has center of 0,0,0 and radius of 0.5 - if (findRaySphereIntersection(entityFrameOrigin, entityFrameDirection, glm::vec3(0.0f), 0.5f, localDistance)) { - // determine where on the unit sphere the hit point occured - glm::vec3 entityFrameHitAt = entityFrameOrigin + (entityFrameDirection * localDistance); - // then translate back to work coordinates - glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f)); - distance = glm::distance(origin, hitAt); + if (findRaySphereIntersection(entityFrameOrigin, entityFrameDirection, glm::vec3(0.0f), 0.5f, distance)) { bool success; - // FIXME: this is only correct for uniformly scaled spheres - surfaceNormal = glm::normalize(hitAt - getCenterPosition(success)); - if (!success) { + glm::vec3 center = getCenterPosition(success); + if (success) { + // FIXME: this is only correct for uniformly scaled spheres + // determine where on the unit sphere the hit point occured + glm::vec3 hitAt = origin + (direction * distance); + surfaceNormal = glm::normalize(hitAt - center); + } else { return false; } return true; @@ -297,9 +295,11 @@ bool ShapeEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, // NOTE: unit sphere has center of 0,0,0 and radius of 0.5 if (findParabolaSphereIntersection(entityFrameOrigin, entityFrameVelocity, entityFrameAcceleration, glm::vec3(0.0f), 0.5f, parabolicDistance)) { bool success; - // FIXME: this is only correct for uniformly scaled spheres - surfaceNormal = glm::normalize((origin + velocity * parabolicDistance + 0.5f * acceleration * parabolicDistance * parabolicDistance) - getCenterPosition(success)); - if (!success) { + glm::vec3 center = getCenterPosition(success); + if (success) { + // FIXME: this is only correct for uniformly scaled spheres + surfaceNormal = glm::normalize((origin + velocity * parabolicDistance + 0.5f * acceleration * parabolicDistance * parabolicDistance) - center); + } else { return false; } return true; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index a7dfa1a41e..c0ae61fdba 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -42,6 +42,7 @@ ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID) : EntityItem(en _shapeType = DEFAULT_SHAPE_TYPE; _compoundShapeURL = DEFAULT_COMPOUND_SHAPE_URL; + _visuallyReady = false; } EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index fc94236c96..fdebb16bc8 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -28,6 +28,20 @@ #include #include +#if defined(Q_OS_ANDROID) +#define FBX_PACK_NORMALS 0 +#else +#define FBX_PACK_NORMALS 1 +#endif + +#if FBX_PACK_NORMALS +using NormalType = glm::uint32; +#define FBX_NORMAL_ELEMENT gpu::Element::VEC4F_NORMALIZED_XYZ10W2 +#else +using NormalType = glm::vec3; +#define FBX_NORMAL_ELEMENT gpu::Element::VEC3F_XYZ +#endif + // See comment in FBXReader::parseFBX(). static const int FBX_HEADER_BYTES_BEFORE_VERSION = 23; static const QByteArray FBX_BINARY_PROLOG("Kaydara FBX Binary "); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index ad002b237c..c391ea6647 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -34,20 +34,6 @@ class QIODevice; class FBXNode; -#if defined(Q_OS_ANDROID) -#define FBX_PACK_NORMALS 0 -#else -#define FBX_PACK_NORMALS 1 -#endif - -#if FBX_PACK_NORMALS -using NormalType = glm::uint32; -#define FBX_NORMAL_ELEMENT gpu::Element::VEC4F_NORMALIZED_XYZ10W2 -#else -using NormalType = glm::vec3; -#define FBX_NORMAL_ELEMENT gpu::Element::VEC3F_XYZ -#endif - /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index 801edddb06..e8b5abcc9c 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -585,13 +585,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { FBXMesh& fbxMesh = extractedMesh; graphics::MeshPointer mesh(new graphics::Mesh()); - - // Grab the vertices in a buffer - auto vb = std::make_shared(); - vb->setData(extractedMesh.vertices.size() * sizeof(glm::vec3), - (const gpu::Byte*) extractedMesh.vertices.data()); - gpu::BufferView vbv(vb, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - mesh->setVertexBuffer(vbv); + bool hasBlendShapes = !fbxMesh.blendshapes.empty(); + int numVerts = extractedMesh.vertices.size(); if (!fbxMesh.normals.empty() && fbxMesh.tangents.empty()) { // Fill with a dummy value to force tangents to be present if there are normals @@ -607,43 +602,61 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { } } - // evaluate all attribute channels sizes - const int normalsSize = fbxMesh.normals.size() * sizeof(NormalType); - const int tangentsSize = fbxMesh.tangents.size() * sizeof(NormalType); + // evaluate all attribute elements and data sizes + + // Position is a vec3 + const auto positionElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + const int positionsSize = numVerts * positionElement.getSize(); + + // Normal and tangent are always there together packed in normalized xyz32bits word (times 2) + const auto normalElement = FBX_NORMAL_ELEMENT; + const int normalsSize = fbxMesh.normals.size() * normalElement.getSize(); + const int tangentsSize = fbxMesh.tangents.size() * normalElement.getSize(); // If there are normals then there should be tangents - assert(normalsSize <= tangentsSize); if (tangentsSize > normalsSize) { qWarning() << "Unexpected tangents in " << url; } const auto normalsAndTangentsSize = normalsSize + tangentsSize; - const int normalsAndTangentsStride = 2 * sizeof(NormalType); - const int colorsSize = fbxMesh.colors.size() * sizeof(ColorType); + const int normalsAndTangentsStride = 2 * normalElement.getSize(); + + // Color attrib + const auto colorElement = FBX_COLOR_ELEMENT; + const int colorsSize = fbxMesh.colors.size() * colorElement.getSize(); + // Texture coordinates are stored in 2 half floats - const int texCoordsSize = fbxMesh.texCoords.size() * sizeof(vec2h); - const int texCoords1Size = fbxMesh.texCoords1.size() * sizeof(vec2h); + const auto texCoordsElement = gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV); + const int texCoordsSize = fbxMesh.texCoords.size() * texCoordsElement.getSize(); + const int texCoords1Size = fbxMesh.texCoords1.size() * texCoordsElement.getSize(); - int clusterIndicesSize = fbxMesh.clusterIndices.size() * sizeof(uint8_t); - if (fbxMesh.clusters.size() > UINT8_MAX) { - // we need 16 bits instead of just 8 for clusterIndices - clusterIndicesSize *= 2; - } + // Support for 4 skinning clusters: + // 4 Indices are uint8 ideally, uint16 if more than 256. + const auto clusterIndiceElement = (fbxMesh.clusters.size() < UINT8_MAX ? gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW) : gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW)); + // 4 Weights are normalized 16bits + const auto clusterWeightElement = gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW); - const int clusterWeightsSize = fbxMesh.clusterWeights.size() * sizeof(uint16_t); + // Cluster indices and weights must be the same sizes + const int NUM_CLUSTERS_PER_VERT = 4; + const int numVertClusters = (fbxMesh.clusterIndices.size() == fbxMesh.clusterWeights.size() ? fbxMesh.clusterIndices.size() / NUM_CLUSTERS_PER_VERT : 0); + const int clusterIndicesSize = numVertClusters * clusterIndiceElement.getSize(); + const int clusterWeightsSize = numVertClusters * clusterWeightElement.getSize(); - // Normals and tangents are interleaved - const int normalsOffset = 0; - const int tangentsOffset = normalsOffset + sizeof(NormalType); - const int colorsOffset = normalsOffset + normalsSize + tangentsSize; + // Decide on where to put what seequencially in a big buffer: + const int positionsOffset = 0; + const int normalsAndTangentsOffset = positionsOffset + positionsSize; + const int colorsOffset = normalsAndTangentsOffset + normalsAndTangentsSize; const int texCoordsOffset = colorsOffset + colorsSize; const int texCoords1Offset = texCoordsOffset + texCoordsSize; const int clusterIndicesOffset = texCoords1Offset + texCoords1Size; const int clusterWeightsOffset = clusterIndicesOffset + clusterIndicesSize; - const int totalAttributeSize = clusterWeightsOffset + clusterWeightsSize; + const int totalVertsSize = clusterWeightsOffset + clusterWeightsSize; - // Copy all attribute data in a single attribute buffer - auto attribBuffer = std::make_shared(); - attribBuffer->resize(totalAttributeSize); + // Copy all vertex data in a single buffer + auto vertBuffer = std::make_shared(); + vertBuffer->resize(totalVertsSize); + + // First positions + vertBuffer->setSubData(positionsOffset, positionsSize, (const gpu::Byte*) extractedMesh.vertices.data()); // Interleave normals and tangents if (normalsSize > 0) { @@ -651,8 +664,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { normalsAndTangents.reserve(fbxMesh.normals.size() + fbxMesh.tangents.size()); for (auto normalIt = fbxMesh.normals.constBegin(), tangentIt = fbxMesh.tangents.constBegin(); - normalIt != fbxMesh.normals.constEnd(); - ++normalIt, ++tangentIt) { + normalIt != fbxMesh.normals.constEnd(); + ++normalIt, ++tangentIt) { #if FBX_PACK_NORMALS const auto normal = normalizeDirForPacking(*normalIt); const auto tangent = normalizeDirForPacking(*tangentIt); @@ -665,9 +678,10 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { normalsAndTangents.push_back(packedNormal); normalsAndTangents.push_back(packedTangent); } - attribBuffer->setSubData(normalsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.data()); + vertBuffer->setSubData(normalsAndTangentsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.data()); } + // Pack colors if (colorsSize > 0) { #if FBX_PACK_COLORS std::vector colors; @@ -676,12 +690,13 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { for (const auto& color : fbxMesh.colors) { colors.push_back(glm::packUnorm4x8(glm::vec4(color, 1.0f))); } - attribBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) colors.data()); + vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) colors.data()); #else - attribBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) fbxMesh.colors.constData()); + vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) fbxMesh.colors.constData()); #endif } + // Pack Texcoords 0 and 1 (if exists) if (texCoordsSize > 0) { QVector texCoordData; texCoordData.reserve(fbxMesh.texCoords.size()); @@ -692,9 +707,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); texCoordData.push_back(texCoordVec2h); } - attribBuffer->setSubData(texCoordsOffset, texCoordsSize, (const gpu::Byte*) texCoordData.constData()); + vertBuffer->setSubData(texCoordsOffset, texCoordsSize, (const gpu::Byte*) texCoordData.constData()); } - if (texCoords1Size > 0) { QVector texCoordData; texCoordData.reserve(fbxMesh.texCoords1.size()); @@ -705,69 +719,170 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); texCoordData.push_back(texCoordVec2h); } - attribBuffer->setSubData(texCoords1Offset, texCoords1Size, (const gpu::Byte*) texCoordData.constData()); + vertBuffer->setSubData(texCoords1Offset, texCoords1Size, (const gpu::Byte*) texCoordData.constData()); } - if (fbxMesh.clusters.size() < UINT8_MAX) { - // yay! we can fit the clusterIndices within 8-bits - int32_t numIndices = fbxMesh.clusterIndices.size(); - QVector clusterIndices; - clusterIndices.resize(numIndices); - for (int32_t i = 0; i < numIndices; ++i) { - assert(fbxMesh.clusterIndices[i] <= UINT8_MAX); - clusterIndices[i] = (uint8_t)(fbxMesh.clusterIndices[i]); + // Clusters data + if (clusterIndicesSize > 0) { + if (fbxMesh.clusters.size() < UINT8_MAX) { + // yay! we can fit the clusterIndices within 8-bits + int32_t numIndices = fbxMesh.clusterIndices.size(); + QVector clusterIndices; + clusterIndices.resize(numIndices); + for (int32_t i = 0; i < numIndices; ++i) { + assert(fbxMesh.clusterIndices[i] <= UINT8_MAX); + clusterIndices[i] = (uint8_t)(fbxMesh.clusterIndices[i]); + } + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); + } else { + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) fbxMesh.clusterIndices.constData()); } - attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); - } else { - attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) fbxMesh.clusterIndices.constData()); } - attribBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) fbxMesh.clusterWeights.constData()); + if (clusterWeightsSize > 0) { + vertBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) fbxMesh.clusterWeights.constData()); + } - if (normalsSize) { - mesh->addAttribute(gpu::Stream::NORMAL, - graphics::BufferView(attribBuffer, normalsOffset, normalsAndTangentsSize, - normalsAndTangentsStride, FBX_NORMAL_ELEMENT)); - mesh->addAttribute(gpu::Stream::TANGENT, - graphics::BufferView(attribBuffer, tangentsOffset, normalsAndTangentsSize, - normalsAndTangentsStride, FBX_NORMAL_ELEMENT)); + + // Now we decide on how to interleave the attributes and provide the vertices among bufers: + // Aka the Vertex format and the vertexBufferStream + auto vertexFormat = std::make_shared(); + auto vertexBufferStream = std::make_shared(); + + // Decision time: + // if blendshapes then keep position and normals/tangents as separated channel buffers from interleaved attributes + // else everything is interleaved in one buffer + + // Default case is no blend shapes + gpu::BufferPointer attribBuffer; + int totalAttribBufferSize = totalVertsSize; + gpu::uint8 posChannel = 0; + gpu::uint8 tangentChannel = posChannel; + gpu::uint8 attribChannel = posChannel; + bool interleavePositions = true; + bool interleaveNormalsTangents = true; + + // TODO: We are using the same vertex format layout for all meshes because this is more efficient + // This work is going into rc73 release which is meant to be used for the SPot500 event and we are picking the format + // that works best for blendshaped and skinned meshes aka the avatars. + // We will improve this technique in a hot fix to 73. + hasBlendShapes = true; + + // If has blend shapes allocate and assign buffers for pos and tangents now + if (hasBlendShapes) { + auto posBuffer = std::make_shared(); + posBuffer->setData(positionsSize, (const gpu::Byte*) vertBuffer->getData() + positionsOffset); + vertexBufferStream->addBuffer(posBuffer, 0, positionElement.getSize()); + + auto normalsAndTangentsBuffer = std::make_shared(); + normalsAndTangentsBuffer->setData(normalsAndTangentsSize, (const gpu::Byte*) vertBuffer->getData() + normalsAndTangentsOffset); + vertexBufferStream->addBuffer(normalsAndTangentsBuffer, 0, normalsAndTangentsStride); + + // update channels and attribBuffer size accordingly + interleavePositions = false; + interleaveNormalsTangents = false; + + tangentChannel = 1; + attribChannel = 2; + + totalAttribBufferSize = totalVertsSize - positionsSize - normalsAndTangentsSize; } + + // Define the vertex format, compute the offset for each attributes as we append them to the vertex format + gpu::Offset bufOffset = 0; + if (positionsSize) { + vertexFormat->setAttribute(gpu::Stream::POSITION, posChannel, positionElement, bufOffset); + bufOffset += positionElement.getSize(); + if (!interleavePositions) { + bufOffset = 0; + } + } + if (normalsSize) { + vertexFormat->setAttribute(gpu::Stream::NORMAL, tangentChannel, normalElement, bufOffset); + bufOffset += normalElement.getSize(); + vertexFormat->setAttribute(gpu::Stream::TANGENT, tangentChannel, normalElement, bufOffset); + bufOffset += normalElement.getSize(); + if (!interleaveNormalsTangents) { + bufOffset = 0; + } + } + + // Pack normal and Tangent with the rest of atributes if no blend shapes if (colorsSize) { - mesh->addAttribute(gpu::Stream::COLOR, - graphics::BufferView(attribBuffer, colorsOffset, colorsSize, FBX_COLOR_ELEMENT)); + vertexFormat->setAttribute(gpu::Stream::COLOR, attribChannel, colorElement, bufOffset); + bufOffset += colorElement.getSize(); } if (texCoordsSize) { - mesh->addAttribute(gpu::Stream::TEXCOORD, - graphics::BufferView( attribBuffer, texCoordsOffset, texCoordsSize, - gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); + vertexFormat->setAttribute(gpu::Stream::TEXCOORD, attribChannel, texCoordsElement, bufOffset); + bufOffset += texCoordsElement.getSize(); } if (texCoords1Size) { - mesh->addAttribute( gpu::Stream::TEXCOORD1, - graphics::BufferView(attribBuffer, texCoords1Offset, texCoords1Size, - gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); + vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, attribChannel, texCoordsElement, bufOffset); + bufOffset += texCoordsElement.getSize(); } else if (texCoordsSize) { - mesh->addAttribute(gpu::Stream::TEXCOORD1, - graphics::BufferView(attribBuffer, texCoordsOffset, texCoordsSize, - gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); + vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, attribChannel, texCoordsElement, bufOffset - texCoordsElement.getSize()); } - if (clusterIndicesSize) { - if (fbxMesh.clusters.size() < UINT8_MAX) { - mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, - graphics::BufferView(attribBuffer, clusterIndicesOffset, clusterIndicesSize, - gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW))); - } else { - mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, - graphics::BufferView(attribBuffer, clusterIndicesOffset, clusterIndicesSize, - gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW))); - } + vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, attribChannel, clusterIndiceElement, bufOffset); + bufOffset += clusterIndiceElement.getSize(); } if (clusterWeightsSize) { - mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, - graphics::BufferView(attribBuffer, clusterWeightsOffset, clusterWeightsSize, - gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW))); + vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, attribChannel, clusterWeightElement, bufOffset); + bufOffset += clusterWeightElement.getSize(); } + // Finally, allocate and fill the attribBuffer interleaving the attributes as needed: + { + auto vPositionOffset = 0; + auto vPositionSize = (interleavePositions ? positionsSize / numVerts : 0); + auto vNormalsAndTangentsOffset = vPositionOffset + vPositionSize; + auto vNormalsAndTangentsSize = (interleaveNormalsTangents ? normalsAndTangentsSize / numVerts : 0); + + auto vColorOffset = vNormalsAndTangentsOffset + vNormalsAndTangentsSize; + auto vColorSize = colorsSize / numVerts; + + auto vTexcoord0Offset = vColorOffset + vColorSize; + auto vTexcoord0Size = texCoordsSize / numVerts; + + auto vTexcoord1Offset = vTexcoord0Offset + vTexcoord0Size; + auto vTexcoord1Size = texCoords1Size / numVerts; + + auto vClusterIndiceOffset = vTexcoord1Offset + vTexcoord1Size; + auto vClusterIndiceSize = clusterIndicesSize / numVerts; + + auto vClusterWeightOffset = vClusterIndiceOffset + vClusterIndiceSize; + auto vClusterWeightSize = clusterWeightsSize / numVerts; + + auto vStride = vClusterWeightOffset + vClusterWeightSize; + + std::vector dest; + dest.resize(totalAttribBufferSize); + auto vDest = dest.data(); + + auto source = vertBuffer->getData(); + + for (int i = 0; i < numVerts; i++) { + + if (vPositionSize) memcpy(vDest + vPositionOffset, source + positionsOffset + i * vPositionSize, vPositionSize); + if (vNormalsAndTangentsSize) memcpy(vDest + vNormalsAndTangentsOffset, source + normalsAndTangentsOffset + i * vNormalsAndTangentsSize, vNormalsAndTangentsSize); + if (vColorSize) memcpy(vDest + vColorOffset, source + colorsOffset + i * vColorSize, vColorSize); + if (vTexcoord0Size) memcpy(vDest + vTexcoord0Offset, source + texCoordsOffset + i * vTexcoord0Size, vTexcoord0Size); + if (vTexcoord1Size) memcpy(vDest + vTexcoord1Offset, source + texCoords1Offset + i * vTexcoord1Size, vTexcoord1Size); + if (vClusterIndiceSize) memcpy(vDest + vClusterIndiceOffset, source + clusterIndicesOffset + i * vClusterIndiceSize, vClusterIndiceSize); + if (vClusterWeightSize) memcpy(vDest + vClusterWeightOffset, source + clusterWeightsOffset + i * vClusterWeightSize, vClusterWeightSize); + + vDest += vStride; + } + + auto attribBuffer = std::make_shared(); + attribBuffer->setData(totalAttribBufferSize, dest.data()); + vertexBufferStream->addBuffer(attribBuffer, 0, vStride); + } + + // Mesh vertex format and vertex stream is ready + mesh->setVertexFormatAndStream(vertexFormat, vertexBufferStream); + + // Index and Part Buffers unsigned int totalIndices = 0; foreach(const FBXMeshPart& part, extractedMesh.parts) { totalIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); diff --git a/libraries/fbx/src/GLTFReader.cpp b/libraries/fbx/src/GLTFReader.cpp index 300e6f4846..317342b886 100644 --- a/libraries/fbx/src/GLTFReader.cpp +++ b/libraries/fbx/src/GLTFReader.cpp @@ -935,7 +935,7 @@ FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping } bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) { - QUrl binaryUrl = _url.resolved(QUrl(url).fileName()); + QUrl binaryUrl = _url.resolved(url); qCDebug(modelformat) << "binaryUrl: " << binaryUrl << " OriginalUrl: " << _url; bool success; @@ -948,7 +948,7 @@ bool GLTFReader::doesResourceExist(const QString& url) { if (_url.isEmpty()) { return false; } - QUrl candidateUrl = _url.resolved(QUrl(url).fileName()); + QUrl candidateUrl = _url.resolved(url); return DependencyManager::get()->resourceExists(candidateUrl); } @@ -1001,8 +1001,9 @@ FBXTexture GLTFReader::getFBXTexture(const GLTFTexture& texture) { fbxtex.texcoordSet = 0; if (texture.defined["source"]) { - QString fname = QUrl(_file.images[texture.source].uri).fileName(); - QUrl textureUrl = _url.resolved(fname); + QString url = _file.images[texture.source].uri; + QString fname = QUrl(url).fileName(); + QUrl textureUrl = _url.resolved(url); qCDebug(modelformat) << "fname: " << fname; qCDebug(modelformat) << "textureUrl: " << textureUrl; qCDebug(modelformat) << "Url: " << _url; diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index caac08f777..c46a1e234c 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -263,15 +263,19 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { default: materials[matName] = currentMaterial; #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Last material illumination model:" << currentMaterial.illuminationModel << - " shininess:" << currentMaterial.shininess << " opacity:" << currentMaterial.opacity << - " diffuse color:" << currentMaterial.diffuseColor << " specular color:" << - currentMaterial.specularColor << " emissive color:" << currentMaterial.emissiveColor << - " diffuse texture:" << currentMaterial.diffuseTextureFilename << " specular texture:" << - currentMaterial.specularTextureFilename << " emissive texture:" << - currentMaterial.emissiveTextureFilename << " bump texture:" << - currentMaterial.bumpTextureFilename; - #endif + qCDebug(modelformat) << + "OBJ Reader Last material illumination model:" << currentMaterial.illuminationModel << + " shininess:" << currentMaterial.shininess << + " opacity:" << currentMaterial.opacity << + " diffuse color:" << currentMaterial.diffuseColor << + " specular color:" << currentMaterial.specularColor << + " emissive color:" << currentMaterial.emissiveColor << + " diffuse texture:" << currentMaterial.diffuseTextureFilename << + " specular texture:" << currentMaterial.specularTextureFilename << + " emissive texture:" << currentMaterial.emissiveTextureFilename << + " bump texture:" << currentMaterial.bumpTextureFilename << + " opacity texture:" << currentMaterial.opacityTextureFilename; +#endif return; } QByteArray token = tokenizer.getDatum(); @@ -289,6 +293,8 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.emissiveTextureFilename = ""; currentMaterial.specularTextureFilename = ""; currentMaterial.bumpTextureFilename = ""; + currentMaterial.opacityTextureFilename = ""; + } else if (token == "Ns") { currentMaterial.shininess = tokenizer.getFloat(); } else if (token == "Ni") { @@ -321,7 +327,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.emissiveColor = tokenizer.getVec3(); } else if (token == "Ks") { currentMaterial.specularColor = tokenizer.getVec3(); - } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump") || (token == "bump")) { + } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump") || (token == "bump") || (token == "map_d")) { const QByteArray textureLine = tokenizer.getLineAsDatum(); QByteArray filename; OBJMaterialTextureOptions textureOptions; @@ -341,6 +347,8 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } else if ((token == "map_bump") || (token == "bump")) { currentMaterial.bumpTextureFilename = filename; currentMaterial.bumpTextureOptions = textureOptions; + } else if (token == "map_d") { + currentMaterial.opacityTextureFilename = filename; } } } @@ -900,6 +908,9 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m fbxMaterial.normalTexture.isBumpmap = true; fbxMaterial.bumpMultiplier = objMaterial.bumpTextureOptions.bumpMultiplier; } + if (!objMaterial.opacityTextureFilename.isEmpty()) { + fbxMaterial.opacityTexture.filename = objMaterial.opacityTextureFilename; + } modelMaterial->setEmissive(fbxMaterial.emissiveColor); modelMaterial->setAlbedo(fbxMaterial.diffuseColor); diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 13ddc6e21c..e432a3ea51 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -66,6 +66,8 @@ public: QByteArray specularTextureFilename; QByteArray emissiveTextureFilename; QByteArray bumpTextureFilename; + QByteArray opacityTextureFilename; + OBJMaterialTextureOptions bumpTextureOptions; int illuminationModel; bool used { false }; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 01424c2ac6..4fea4f2dc5 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -201,9 +201,10 @@ void GLBackend::renderPassTransfer(const Batch& batch) { { Vec2u outputSize{ 1,1 }; - if (_output._framebuffer) { - outputSize.x = _output._framebuffer->getWidth(); - outputSize.y = _output._framebuffer->getHeight(); + auto framebuffer = acquire(_output._framebuffer); + if (framebuffer) { + outputSize.x = framebuffer->getWidth(); + outputSize.y = framebuffer->getHeight(); } else if (glm::dot(_transform._projectionJitter, _transform._projectionJitter)>0.0f) { qCWarning(gpugllogging) << "Jittering needs to have a frame buffer to be set"; } @@ -220,6 +221,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) { _stereo._contextDisable = false; break; + case Batch::COMMAND_setFramebuffer: case Batch::COMMAND_setViewportTransform: case Batch::COMMAND_setViewTransform: case Batch::COMMAND_setProjectionTransform: @@ -765,7 +767,6 @@ void GLBackend::recycle() const { } _textureManagement._transferEngine->manageMemory(); - Texture::KtxStorage::releaseOpenKtxFiles(); } void GLBackend::setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset) { diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index cadcec7a56..0b76ef17de 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -30,7 +30,6 @@ #include "GLShared.h" - // Different versions for the stereo drawcall // Current preferred is "instanced" which draw the shape twice but instanced and rely on clipping plane to draw left/right side only #if defined(USE_GLES) @@ -40,7 +39,6 @@ #define GPU_STEREO_TECHNIQUE_INSTANCED #endif - // Let these be configured by the one define picked above #ifdef GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE #define GPU_STEREO_DRAWCALL_DOUBLED @@ -56,8 +54,153 @@ #define GPU_STEREO_CAMERA_BUFFER #endif +// +// GL Backend pointer storage mechanism +// One of the following three defines must be defined. +// GPU_POINTER_STORAGE_SHARED + +// The platonic ideal, use references to smart pointers. +// However, this produces artifacts because there are too many places in the code right now that +// create temporary values (undesirable smart pointer duplications) and then those temp variables +// get passed on and have their reference taken, and then invalidated +// GPU_POINTER_STORAGE_REF + +// Raw pointer manipulation. Seems more dangerous than the reference wrappers, +// but in practice, the danger of grabbing a reference to a temporary variable +// is causing issues +// GPU_POINTER_STORAGE_RAW + +#if defined(USE_GLES) +#define GPU_POINTER_STORAGE_SHARED +#else +#define GPU_POINTER_STORAGE_RAW +#endif + namespace gpu { namespace gl { +#if defined(GPU_POINTER_STORAGE_SHARED) +template +static inline bool compare(const std::shared_ptr& a, const std::shared_ptr& b) { + return a == b; +} + +template +static inline T* acquire(const std::shared_ptr& pointer) { + return pointer.get(); +} + +template +static inline void reset(std::shared_ptr& pointer) { + return pointer.reset(); +} + +template +static inline bool valid(const std::shared_ptr& pointer) { + return pointer.operator bool(); +} + +template +static inline void assign(std::shared_ptr& pointer, const std::shared_ptr& source) { + pointer = source; +} + +using BufferReference = BufferPointer; +using TextureReference = TexturePointer; +using FramebufferReference = FramebufferPointer; +using FormatReference = Stream::FormatPointer; +using PipelineReference = PipelinePointer; + +#define GPU_REFERENCE_INIT_VALUE nullptr + +#elif defined(GPU_POINTER_STORAGE_REF) + +template +class PointerReferenceWrapper : public std::reference_wrapper> { + using Parent = std::reference_wrapper>; + +public: + using Pointer = std::shared_ptr; + PointerReferenceWrapper() : Parent(EMPTY()) {} + PointerReferenceWrapper(const Pointer& pointer) : Parent(pointer) {} + void clear() { *this = EMPTY(); } + +private: + static const Pointer& EMPTY() { + static const Pointer EMPTY_VALUE; + return EMPTY_VALUE; + }; +}; + +template +static bool compare(const PointerReferenceWrapper& reference, const std::shared_ptr& pointer) { + return reference.get() == pointer; +} + +template +static inline T* acquire(const PointerReferenceWrapper& reference) { + return reference.get().get(); +} + +template +static void assign(PointerReferenceWrapper& reference, const std::shared_ptr& pointer) { + reference = pointer; +} + +template +static bool valid(const PointerReferenceWrapper& reference) { + return reference.get().operator bool(); +} + +template +static inline void reset(PointerReferenceWrapper& reference) { + return reference.clear(); +} + +using BufferReference = PointerReferenceWrapper; +using TextureReference = PointerReferenceWrapper; +using FramebufferReference = PointerReferenceWrapper; +using FormatReference = PointerReferenceWrapper; +using PipelineReference = PointerReferenceWrapper; + +#define GPU_REFERENCE_INIT_VALUE + +#elif defined(GPU_POINTER_STORAGE_RAW) + +template +static bool compare(const T* const& rawPointer, const std::shared_ptr& pointer) { + return rawPointer == pointer.get(); +} + +template +static inline T* acquire(T*& rawPointer) { + return rawPointer; +} + +template +static inline bool valid(const T* const& rawPointer) { + return rawPointer; +} + +template +static inline void reset(T*& rawPointer) { + rawPointer = nullptr; +} + +template +static inline void assign(T*& rawPointer, const std::shared_ptr& pointer) { + rawPointer = pointer.get(); +} + +using BufferReference = Buffer*; +using TextureReference = Texture*; +using FramebufferReference = Framebuffer*; +using FormatReference = Stream::Format*; +using PipelineReference = Pipeline*; + +#define GPU_REFERENCE_INIT_VALUE nullptr + +#endif + class GLBackend : public Backend, public std::enable_shared_from_this { // Context Backend static interface required friend class gpu::Context; @@ -67,8 +210,8 @@ class GLBackend : public Backend, public std::enable_shared_from_this protected: explicit GLBackend(bool syncCache); GLBackend(); -public: +public: #if defined(USE_GLES) // https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glGet.xhtml static const GLint MIN_REQUIRED_TEXTURE_IMAGE_UNITS = 16; @@ -109,8 +252,8 @@ public: // This is the ugly "download the pixels to sysmem for taking a snapshot" // Just avoid using it, it's ugly and will break performances virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, - const Vec4i& region, QImage& destImage) final override; - + const Vec4i& region, + QImage& destImage) final override; // this is the maximum numeber of available input buffers size_t getNumInputBuffers() const { return _input._invalidBuffers.size(); } @@ -131,7 +274,6 @@ public: static const int MAX_NUM_RESOURCE_TABLE_TEXTURES = 2; size_t getMaxNumResourceTextureTables() const { return MAX_NUM_RESOURCE_TABLE_TEXTURES; } - // Draw Stage virtual void do_draw(const Batch& batch, size_t paramOffset) = 0; virtual void do_drawIndexed(const Batch& batch, size_t paramOffset) = 0; @@ -183,7 +325,6 @@ public: // Reset stages virtual void do_resetStages(const Batch& batch, size_t paramOffset) final; - virtual void do_disableContextViewCorrection(const Batch& batch, size_t paramOffset) final; virtual void do_restoreContextViewCorrection(const Batch& batch, size_t paramOffset) final; @@ -203,7 +344,7 @@ public: virtual void do_popProfileRange(const Batch& batch, size_t paramOffset) final; // TODO: As long as we have gl calls explicitely issued from interface - // code, we need to be able to record and batch these calls. THe long + // code, we need to be able to record and batch these calls. THe long // term strategy is to get rid of any GL calls in favor of the HIFI GPU API virtual void do_glUniform1i(const Batch& batch, size_t paramOffset) final; virtual void do_glUniform1f(const Batch& batch, size_t paramOffset) final; @@ -228,7 +369,9 @@ public: virtual void do_setStateAntialiasedLineEnable(bool enable) final; virtual void do_setStateDepthBias(Vec2 bias) final; virtual void do_setStateDepthTest(State::DepthTest test) final; - virtual void do_setStateStencil(State::StencilActivation activation, State::StencilTest frontTest, State::StencilTest backTest) final; + virtual void do_setStateStencil(State::StencilActivation activation, + State::StencilTest frontTest, + State::StencilTest backTest) final; virtual void do_setStateAlphaToCoverageEnable(bool enable) final; virtual void do_setStateSampleMask(uint32 mask) final; virtual void do_setStateBlend(State::BlendFunction blendFunction) final; @@ -239,6 +382,7 @@ public: virtual GLuint getFramebufferID(const FramebufferPointer& framebuffer) = 0; virtual GLuint getTextureID(const TexturePointer& texture) final; virtual GLuint getBufferID(const Buffer& buffer) = 0; + virtual GLuint getBufferIDUnsynced(const Buffer& buffer) = 0; virtual GLuint getQueryID(const QueryPointer& query) = 0; virtual GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) = 0; @@ -256,7 +400,9 @@ public: virtual void releaseQuery(GLuint id) const; virtual void queueLambda(const std::function lambda) const; - bool isTextureManagementSparseEnabled() const override { return (_textureManagement._sparseCapable && Texture::getEnableSparseTextures()); } + bool isTextureManagementSparseEnabled() const override { + return (_textureManagement._sparseCapable && Texture::getEnableSparseTextures()); + } protected: virtual GLint getRealUniformLocation(GLint location) const; @@ -265,11 +411,11 @@ protected: // FIXME instead of a single flag, create a features struct similar to // https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkPhysicalDeviceFeatures.html - virtual bool supportsBindless() const { return false; } + virtual bool supportsBindless() const { return false; } static const size_t INVALID_OFFSET = (size_t)-1; - bool _inRenderTransferPass { false }; - int _currentDraw { -1 }; + bool _inRenderTransferPass{ false }; + int _currentDraw{ -1 }; std::list profileRanges; mutable Mutex _trashMutex; @@ -298,46 +444,36 @@ protected: virtual void updateInput() = 0; struct InputStageState { - bool _invalidFormat { true }; - bool _lastUpdateStereoState{ false }; + bool _invalidFormat{ true }; + bool _lastUpdateStereoState{ false }; bool _hadColorAttribute{ true }; - Stream::FormatPointer _format; + FormatReference _format{ GPU_REFERENCE_INIT_VALUE }; std::string _formatKey; typedef std::bitset ActivationCache; - ActivationCache _attributeActivation { 0 }; + ActivationCache _attributeActivation{ 0 }; typedef std::bitset BuffersState; BuffersState _invalidBuffers{ 0 }; BuffersState _attribBindingBuffers{ 0 }; - Buffers _buffers; - Offsets _bufferOffsets; - Offsets _bufferStrides; - std::vector _bufferVBOs; + std::array _buffers{}; + std::array _bufferOffsets{}; + std::array _bufferStrides{}; + std::array _bufferVBOs{}; glm::vec4 _colorAttribute{ 0.0f }; - BufferPointer _indexBuffer; - Offset _indexBufferOffset { 0 }; - Type _indexBufferType { UINT32 }; - - BufferPointer _indirectBuffer; + BufferReference _indexBuffer{}; + Offset _indexBufferOffset{ 0 }; + Type _indexBufferType{ UINT32 }; + + BufferReference _indirectBuffer{}; Offset _indirectBufferOffset{ 0 }; Offset _indirectBufferStride{ 0 }; - GLuint _defaultVAO { 0 }; - - InputStageState() : - _invalidFormat(true), - _format(0), - _formatKey(), - _attributeActivation(0), - _buffers(_invalidBuffers.size(), BufferPointer(0)), - _bufferOffsets(_invalidBuffers.size(), 0), - _bufferStrides(_invalidBuffers.size(), 0), - _bufferVBOs(_invalidBuffers.size(), 0) {} + GLuint _defaultVAO{ 0 }; } _input; virtual void initTransform() = 0; @@ -348,7 +484,7 @@ protected: virtual void resetTransformStage(); // Allows for correction of the camera pose to account for changes - // between the time when a was recorded and the time(s) when it is + // between the time when a was recorded and the time(s) when it is // executed // Prev is the previous correction used at previous frame struct CameraCorrection { @@ -363,9 +499,12 @@ protected: struct Cameras { TransformCamera _cams[2]; - Cameras() {}; + Cameras(){}; Cameras(const TransformCamera& cam) { memcpy(_cams, &cam, sizeof(TransformCamera)); }; - Cameras(const TransformCamera& camL, const TransformCamera& camR) { memcpy(_cams, &camL, sizeof(TransformCamera)); memcpy(_cams + 1, &camR, sizeof(TransformCamera)); }; + Cameras(const TransformCamera& camL, const TransformCamera& camR) { + memcpy(_cams, &camL, sizeof(TransformCamera)); + memcpy(_cams + 1, &camR, sizeof(TransformCamera)); + }; }; using CameraBufferElement = Cameras; @@ -379,25 +518,24 @@ protected: mutable std::map _drawCallInfoOffsets; - GLuint _objectBuffer { 0 }; - GLuint _cameraBuffer { 0 }; - GLuint _drawCallInfoBuffer { 0 }; - GLuint _objectBufferTexture { 0 }; - size_t _cameraUboSize { 0 }; + GLuint _objectBuffer{ 0 }; + GLuint _cameraBuffer{ 0 }; + GLuint _drawCallInfoBuffer{ 0 }; + GLuint _objectBufferTexture{ 0 }; + size_t _cameraUboSize{ 0 }; bool _viewIsCamera{ false }; - bool _skybox { false }; + bool _skybox{ false }; Transform _view; CameraCorrection _correction; bool _viewCorrectionEnabled{ true }; - Mat4 _projection; - Vec4i _viewport { 0, 0, 1, 1 }; - Vec2 _depthRange { 0.0f, 1.0f }; + Vec4i _viewport{ 0, 0, 1, 1 }; + Vec2 _depthRange{ 0.0f, 1.0f }; Vec2 _projectionJitter{ 0.0f, 0.0f }; - bool _invalidView { false }; - bool _invalidProj { false }; - bool _invalidViewport { false }; + bool _invalidView{ false }; + bool _invalidProj{ false }; + bool _invalidViewport{ false }; bool _enabledDrawcallInfoBuffer{ false }; @@ -416,45 +554,54 @@ protected: struct UniformStageState { struct BufferState { - BufferPointer buffer; + BufferReference buffer{}; GLintptr offset{ 0 }; GLsizeiptr size{ 0 }; - BufferState(const BufferPointer& buffer = nullptr, GLintptr offset = 0, GLsizeiptr size = 0); - bool operator ==(BufferState& other) const { - return offset == other.offset && size == other.size && buffer == other.buffer; + + BufferState& operator=(const BufferState& other) = delete; + void reset() { + gpu::gl::reset(buffer); + offset = 0; + size = 0; + } + bool compare(const BufferPointer& buffer, GLintptr offset, GLsizeiptr size) { + const auto& self = *this; + return (self.offset == offset && self.size == size && gpu::gl::compare(self.buffer, buffer)); } }; // MAX_NUM_UNIFORM_BUFFERS-1 is the max uniform index BATCHES are allowed to set, but - // MIN_REQUIRED_UNIFORM_BUFFER_BINDINGS is used here because the backend sets some - // internal UBOs for things like camera correction + // MIN_REQUIRED_UNIFORM_BUFFER_BINDINGS is used here because the backend sets some + // internal UBOs for things like camera correction std::array _buffers; } _uniform; - // Helper function that provides common code + // Helper function that provides common code void bindUniformBuffer(uint32_t slot, const BufferPointer& buffer, GLintptr offset = 0, GLsizeiptr size = 0); void releaseUniformBuffer(uint32_t slot); void resetUniformStage(); // update resource cache and do the gl bind/unbind call with the current gpu::Buffer cached at slot s // This is using different gl object depending on the gl version - virtual bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) = 0; + virtual bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) = 0; virtual void releaseResourceBuffer(uint32_t slot) = 0; - // Helper function that provides common code used by do_setResourceTexture and + // Helper function that provides common code used by do_setResourceTexture and // do_setResourceTextureTable (in non-bindless mode) void bindResourceTexture(uint32_t slot, const TexturePointer& texture); - // update resource cache and do the gl unbind call with the current gpu::Texture cached at slot s void releaseResourceTexture(uint32_t slot); void resetResourceStage(); struct ResourceStageState { - std::array _buffers; - std::array _textures; - //Textures _textures { { MAX_NUM_RESOURCE_TEXTURES } }; + struct TextureState { + TextureReference _texture{}; + GLenum _target; + }; + std::array _buffers{}; + std::array _textures{}; int findEmptyTextureSlot() const; } _resource; @@ -469,21 +616,22 @@ protected: void resetPipelineStage(); struct PipelineStageState { - PipelinePointer _pipeline; + PipelineReference _pipeline{}; - GLuint _program { 0 }; - bool _cameraCorrection { false }; - GLShader* _programShader { nullptr }; - bool _invalidProgram { false }; + GLuint _program{ 0 }; + bool _cameraCorrection{ false }; + GLShader* _programShader{ nullptr }; + bool _invalidProgram{ false }; - BufferView _cameraCorrectionBuffer { gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr )) }; - BufferView _cameraCorrectionBufferIdentity { gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr )) }; + BufferView _cameraCorrectionBuffer{ gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr)) }; + BufferView _cameraCorrectionBufferIdentity{ gpu::BufferView( + std::make_shared(sizeof(CameraCorrection), nullptr)) }; State::Data _stateCache{ State::DEFAULT }; - State::Signature _stateSignatureCache { 0 }; + State::Signature _stateSignatureCache{ 0 }; - GLState* _state { nullptr }; - bool _invalidState { false }; + GLState* _state{ nullptr }; + bool _invalidState{ false }; PipelineStageState() { _cameraCorrectionBuffer.edit() = CameraCorrection(); @@ -497,9 +645,9 @@ protected: virtual GLShader* compileBackendProgram(const Shader& program, const Shader::CompilationHandler& handler); virtual GLShader* compileBackendShader(const Shader& shader, const Shader::CompilationHandler& handler); virtual std::string getBackendShaderHeader() const = 0; - // For a program, this will return a string containing all the source files (without any - // backend headers or defines). For a vertex, fragment or geometry shader, this will - // return the fully customized shader with all the version and backend specific + // For a program, this will return a string containing all the source files (without any + // backend headers or defines). For a vertex, fragment or geometry shader, this will + // return the fully customized shader with all the version and backend specific // preprocessor directives // The program string returned can be used as a key for a cache of shader binaries // The shader strings can be reliably sent to the low level `compileShader` functions @@ -515,22 +663,22 @@ protected: // Synchronize the state cache of this Backend with the actual real state of the GL Context void syncOutputStateCache(); void resetOutputStage(); - + struct OutputStageState { - FramebufferPointer _framebuffer { nullptr }; - GLuint _drawFBO { 0 }; + FramebufferReference _framebuffer{}; + GLuint _drawFBO{ 0 }; } _output; void resetQueryStage(); struct QueryStageState { - uint32_t _rangeQueryDepth { 0 }; + uint32_t _rangeQueryDepth{ 0 }; } _queryStage; void resetStages(); // Stores cached binary versions of the shaders for quicker startup on subsequent runs - // Note that shaders in the cache can still fail to load due to hardware or driver - // changes that invalidate the cached binary, in which case we fall back on compiling + // Note that shaders in the cache can still fail to load due to hardware or driver + // changes that invalidate the cached binary, in which case we fall back on compiling // the source again struct ShaderBinaryCache { std::mutex _mutex; @@ -542,7 +690,7 @@ protected: virtual void killShaderBinaryCache(); struct TextureManagementStageState { - bool _sparseCapable { false }; + bool _sparseCapable{ false }; GLTextureTransferEnginePointer _transferEngine; } _textureManagement; virtual void initTextureManagementStage(); @@ -555,6 +703,6 @@ protected: friend class GLShader; }; -} } +}} // namespace gpu::gl #endif diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp index 77e1f90f66..85e6ba5382 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp @@ -11,15 +11,16 @@ #include "GLBackend.h" #include "GLShared.h" #include "GLInputFormat.h" +#include "GLBuffer.h" using namespace gpu; using namespace gpu::gl; void GLBackend::do_setInputFormat(const Batch& batch, size_t paramOffset) { - Stream::FormatPointer format = batch._streamFormats.get(batch._params[paramOffset]._uint); - if (format != _input._format) { - _input._format = format; + const auto& format = batch._streamFormats.get(batch._params[paramOffset]._uint); + if (!compare(_input._format, format)) { if (format) { + assign(_input._format, format); auto inputFormat = GLInputFormat::sync((*format)); assert(inputFormat); if (_input._formatKey != inputFormat->key) { @@ -27,6 +28,7 @@ void GLBackend::do_setInputFormat(const Batch& batch, size_t paramOffset) { _input._invalidFormat = true; } } else { + reset(_input._format); _input._formatKey.clear(); _input._invalidFormat = true; } @@ -36,20 +38,14 @@ void GLBackend::do_setInputFormat(const Batch& batch, size_t paramOffset) { void GLBackend::do_setInputBuffer(const Batch& batch, size_t paramOffset) { Offset stride = batch._params[paramOffset + 0]._uint; Offset offset = batch._params[paramOffset + 1]._uint; - BufferPointer buffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); + const auto& buffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); uint32 channel = batch._params[paramOffset + 3]._uint; if (channel < getNumInputBuffers()) { bool isModified = false; - if (_input._buffers[channel] != buffer) { - _input._buffers[channel] = buffer; - - GLuint vbo = 0; - if (buffer) { - vbo = getBufferID((*buffer)); - } - _input._bufferVBOs[channel] = vbo; - + if (!compare(_input._buffers[channel], buffer)) { + assign(_input._buffers[channel], buffer); + _input._bufferVBOs[channel] = getBufferIDUnsynced((*buffer)); isModified = true; } @@ -99,18 +95,18 @@ void GLBackend::resetInputStage() { // Reset index buffer _input._indexBufferType = UINT32; _input._indexBufferOffset = 0; - _input._indexBuffer.reset(); + reset(_input._indexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); (void) CHECK_GL_ERROR(); // Reset vertex buffer and format - _input._format.reset(); + reset(_input._format); _input._formatKey.clear(); _input._invalidFormat = false; _input._attributeActivation.reset(); for (uint32_t i = 0; i < _input._buffers.size(); i++) { - _input._buffers[i].reset(); + reset(_input._buffers[i]); _input._bufferOffsets[i] = 0; _input._bufferStrides[i] = 0; _input._bufferVBOs[i] = 0; @@ -124,11 +120,11 @@ void GLBackend::do_setIndexBuffer(const Batch& batch, size_t paramOffset) { _input._indexBufferType = (Type)batch._params[paramOffset + 2]._uint; _input._indexBufferOffset = batch._params[paramOffset + 0]._uint; - BufferPointer indexBuffer = batch._buffers.get(batch._params[paramOffset + 1]._uint); - if (indexBuffer != _input._indexBuffer) { - _input._indexBuffer = indexBuffer; + const auto& indexBuffer = batch._buffers.get(batch._params[paramOffset + 1]._uint); + if (!compare(_input._indexBuffer, indexBuffer)) { + assign(_input._indexBuffer, indexBuffer); if (indexBuffer) { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getBufferID(*indexBuffer)); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getBufferIDUnsynced(*indexBuffer)); } else { // FIXME do we really need this? Is there ever a draw call where we care that the element buffer is null? glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); @@ -141,11 +137,11 @@ void GLBackend::do_setIndirectBuffer(const Batch& batch, size_t paramOffset) { _input._indirectBufferOffset = batch._params[paramOffset + 1]._uint; _input._indirectBufferStride = batch._params[paramOffset + 2]._uint; - BufferPointer buffer = batch._buffers.get(batch._params[paramOffset]._uint); - if (buffer != _input._indirectBuffer) { - _input._indirectBuffer = buffer; + const auto& buffer = batch._buffers.get(batch._params[paramOffset]._uint); + if (!compare(_input._indirectBuffer, buffer)) { + assign(_input._indirectBuffer, buffer); if (buffer) { - glBindBuffer(GL_DRAW_INDIRECT_BUFFER, getBufferID(*buffer)); + glBindBuffer(GL_DRAW_INDIRECT_BUFFER, getBufferIDUnsynced(*buffer)); } else { // FIXME do we really need this? Is there ever a draw call where we care that the element buffer is null? glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0); @@ -157,7 +153,7 @@ void GLBackend::do_setIndirectBuffer(const Batch& batch, size_t paramOffset) { void GLBackend::updateInput() { bool isStereoNow = isStereo(); - // track stereo state change potentially happening wihtout changing the input format + // track stereo state change potentially happening without changing the input format // this is a rare case requesting to invalid the format #ifdef GPU_STEREO_DRAWCALL_INSTANCED _input._invalidFormat |= (isStereoNow != _input._lastUpdateStereoState); @@ -168,13 +164,14 @@ void GLBackend::updateInput() { InputStageState::ActivationCache newActivation; // Assign the vertex format required - if (_input._format) { + auto format = acquire(_input._format); + if (format) { bool hasColorAttribute{ false }; _input._attribBindingBuffers.reset(); - const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); - auto& inputChannels = _input._format->getChannels(); + const auto& attributes = format->getAttributes(); + const auto& inputChannels = format->getChannels(); for (auto& channelIt : inputChannels) { auto bufferChannelNum = (channelIt).first; const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second; @@ -261,9 +258,17 @@ void GLBackend::updateInput() { auto offset = _input._bufferOffsets.data(); auto stride = _input._bufferStrides.data(); + // Profile the count of buffers to update and use it to short cut the for loop + int numInvalids = (int) _input._invalidBuffers.count(); + _stats._ISNumInputBufferChanges += numInvalids; + for (GLuint buffer = 0; buffer < _input._buffers.size(); buffer++, vbo++, offset++, stride++) { if (_input._invalidBuffers.test(buffer)) { glBindVertexBuffer(buffer, (*vbo), (*offset), (GLsizei)(*stride)); + numInvalids--; + if (numInvalids <= 0) { + break; + } } } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp index d1ab34da90..411e16b18e 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp @@ -27,12 +27,12 @@ void GLBackend::syncOutputStateCache() { glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO); _output._drawFBO = currentFBO; - _output._framebuffer.reset(); + reset(_output._framebuffer); } void GLBackend::resetOutputStage() { - if (_output._framebuffer) { - _output._framebuffer.reset(); + if (valid(_output._framebuffer)) { + reset(_output._framebuffer); _output._drawFBO = 0; glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } @@ -41,7 +41,7 @@ void GLBackend::resetOutputStage() { } void GLBackend::do_setFramebuffer(const Batch& batch, size_t paramOffset) { - auto framebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); + const auto& framebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); setFramebuffer(framebuffer); } @@ -55,13 +55,13 @@ void GLBackend::do_setFramebufferSwapChain(const Batch& batch, size_t paramOffse } void GLBackend::setFramebuffer(const FramebufferPointer& framebuffer) { - if (_output._framebuffer != framebuffer) { + if (!compare(_output._framebuffer, framebuffer)) { auto newFBO = getFramebufferID(framebuffer); if (_output._drawFBO != newFBO) { _output._drawFBO = newFBO; glBindFramebuffer(GL_DRAW_FRAMEBUFFER, newFBO); } - _output._framebuffer = framebuffer; + assign(_output._framebuffer, framebuffer); } } @@ -114,8 +114,9 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { } std::vector drawBuffers; + auto framebuffer = acquire(_output._framebuffer); if (masks & Framebuffer::BUFFER_COLORS) { - if (_output._framebuffer) { + if (framebuffer) { for (unsigned int i = 0; i < Framebuffer::MAX_NUM_RENDER_BUFFERS; i++) { if (masks & (1 << i)) { drawBuffers.push_back(GL_COLOR_ATTACHMENT0 + i); @@ -163,8 +164,8 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { } // Restore the color draw buffers only if a frmaebuffer is bound - if (_output._framebuffer && !drawBuffers.empty()) { - auto glFramebuffer = syncGPUObject(*_output._framebuffer); + if (framebuffer && !drawBuffers.empty()) { + auto glFramebuffer = syncGPUObject(*framebuffer); if (glFramebuffer) { glDrawBuffers((GLsizei)glFramebuffer->_colorBuffers.size(), glFramebuffer->_colorBuffers.data()); } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index d3d2bc0938..7a06b3af86 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -23,9 +23,9 @@ using namespace gpu; using namespace gpu::gl; void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { - PipelinePointer pipeline = batch._pipelines.get(batch._params[paramOffset + 0]._uint); + const auto& pipeline = batch._pipelines.get(batch._params[paramOffset + 0]._uint); - if (_pipeline._pipeline == pipeline) { + if (compare(_pipeline._pipeline, pipeline)) { return; } @@ -34,7 +34,7 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { // null pipeline == reset if (!pipeline) { - _pipeline._pipeline.reset(); + reset(_pipeline._pipeline); _pipeline._program = 0; _pipeline._cameraCorrection = false; @@ -73,7 +73,7 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { } // Remember the new pipeline - _pipeline._pipeline = pipeline; + assign(_pipeline._pipeline, pipeline); } // THis should be done on Pipeline::update... @@ -81,10 +81,12 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { glUseProgram(_pipeline._program); if (_pipeline._cameraCorrection) { // Invalidate uniform buffer cache slot - _uniform._buffers[gpu::slot::buffer::CameraCorrection] = {}; + _uniform._buffers[gpu::slot::buffer::CameraCorrection].reset(); auto& cameraCorrectionBuffer = _transform._viewCorrectionEnabled ? _pipeline._cameraCorrectionBuffer._buffer : _pipeline._cameraCorrectionBufferIdentity._buffer; + // Because we don't sync Buffers in the bindUniformBuffer, let s force this buffer synced + getBufferID(*cameraCorrectionBuffer); bindUniformBuffer(gpu::slot::buffer::CameraCorrection, cameraCorrectionBuffer, 0, sizeof(CameraCorrection)); } (void)CHECK_GL_ERROR(); @@ -110,7 +112,7 @@ void GLBackend::updatePipeline() { _pipeline._stateSignatureCache |= _pipeline._state->_signature; // And perform - for (auto command : _pipeline._state->_commands) { + for (const auto& command : _pipeline._state->_commands) { command->run(this); } } else { @@ -132,23 +134,17 @@ void GLBackend::resetPipelineStage() { _pipeline._invalidProgram = false; _pipeline._program = 0; _pipeline._programShader = nullptr; - _pipeline._pipeline.reset(); + reset(_pipeline._pipeline); glUseProgram(0); } -GLBackend::UniformStageState::BufferState::BufferState(const BufferPointer& buffer, GLintptr offset, GLsizeiptr size) - : buffer(buffer), offset(offset), size(size) {} - void GLBackend::releaseUniformBuffer(uint32_t slot) { - auto& buf = _uniform._buffers[slot]; - if (buf.buffer) { - auto* object = Backend::getGPUObject(*buf.buffer); - if (object) { - glBindBufferBase(GL_UNIFORM_BUFFER, slot, 0); // RELEASE - (void)CHECK_GL_ERROR(); - } - buf = UniformStageState::BufferState(); + auto& bufferState = _uniform._buffers[slot]; + if (valid(bufferState.buffer)) { + glBindBufferBase(GL_UNIFORM_BUFFER, slot, 0); // RELEASE + (void)CHECK_GL_ERROR(); } + bufferState.reset(); } void GLBackend::resetUniformStage() { @@ -163,19 +159,20 @@ void GLBackend::bindUniformBuffer(uint32_t slot, const BufferPointer& buffer, GL return; } - UniformStageState::BufferState bufferState{ buffer, offset, size }; + auto& currentBufferState = _uniform._buffers[slot]; // check cache before thinking - if (_uniform._buffers[slot] == bufferState) { + if (currentBufferState.compare(buffer, offset, size)) { return; } - // Sync BufferObject - auto* object = syncGPUObject(*bufferState.buffer); - if (object) { - glBindBufferRange(GL_UNIFORM_BUFFER, slot, object->_buffer, bufferState.offset, bufferState.size); - - _uniform._buffers[slot] = bufferState; + // Grab the true gl Buffer object + auto glBO = getBufferIDUnsynced(*buffer); + if (glBO) { + glBindBufferRange(GL_UNIFORM_BUFFER, slot, glBO, offset, size); + assign(currentBufferState.buffer, buffer); + currentBufferState.offset = offset; + currentBufferState.size = size; (void)CHECK_GL_ERROR(); } else { releaseUniformBuffer(slot); @@ -192,7 +189,7 @@ void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) { return; } - BufferPointer uniformBuffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); + const auto& uniformBuffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); GLintptr rangeStart = batch._params[paramOffset + 1]._uint; GLsizeiptr rangeSize = batch._params[paramOffset + 0]._uint; @@ -200,16 +197,12 @@ void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) { } void GLBackend::releaseResourceTexture(uint32_t slot) { - auto& tex = _resource._textures[slot]; - if (tex) { - auto* object = Backend::getGPUObject(*tex); - if (object) { - GLuint target = object->_target; - glActiveTexture(GL_TEXTURE0 + slot); - glBindTexture(target, 0); // RELEASE - (void)CHECK_GL_ERROR(); - } - tex.reset(); + auto& textureState = _resource._textures[slot]; + if (valid(textureState._texture)) { + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(textureState._target, 0); // RELEASE + (void)CHECK_GL_ERROR(); + reset(textureState._texture); } } @@ -231,14 +224,14 @@ void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) { return; } - auto resourceBuffer = batch._buffers.get(batch._params[paramOffset + 0]._uint); + const auto& resourceBuffer = batch._buffers.get(batch._params[paramOffset + 0]._uint); if (!resourceBuffer) { releaseResourceBuffer(slot); return; } // check cache before thinking - if (_resource._buffers[slot] == resourceBuffer) { + if (compare(_resource._buffers[slot], resourceBuffer)) { return; } @@ -247,7 +240,7 @@ void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) { // If successful bind then cache it if (bindResourceBuffer(slot, resourceBuffer)) { - _resource._buffers[slot] = resourceBuffer; + assign(_resource._buffers[slot], resourceBuffer); } else { // else clear slot and cache releaseResourceBuffer(slot); return; @@ -292,14 +285,15 @@ void GLBackend::do_setResourceFramebufferSwapChainTexture(const Batch& batch, si } auto index = batch._params[paramOffset + 2]._uint; auto renderBufferSlot = batch._params[paramOffset + 3]._uint; - auto resourceFramebuffer = swapChain->get(index); - auto resourceTexture = resourceFramebuffer->getRenderBuffer(renderBufferSlot); + const auto& resourceFramebuffer = swapChain->get(index); + const auto& resourceTexture = resourceFramebuffer->getRenderBuffer(renderBufferSlot); setResourceTexture(slot, resourceTexture); } void GLBackend::setResourceTexture(unsigned int slot, const TexturePointer& resourceTexture) { + auto& textureState = _resource._textures[slot]; // check cache before thinking - if (_resource._textures[slot] == resourceTexture) { + if (compare(textureState._texture, resourceTexture)) { return; } @@ -309,15 +303,12 @@ void GLBackend::setResourceTexture(unsigned int slot, const TexturePointer& reso // Always make sure the GLObject is in sync GLTexture* object = syncGPUObject(resourceTexture); if (object) { + assign(textureState._texture, resourceTexture); GLuint to = object->_texture; - GLuint target = object->_target; + textureState._target = object->_target; glActiveTexture(GL_TEXTURE0 + slot); - glBindTexture(target, to); - + glBindTexture(textureState._target, to); (void)CHECK_GL_ERROR(); - - _resource._textures[slot] = resourceTexture; - _stats._RSAmountTextureMemoryBounded += (int)object->size(); } else { @@ -342,7 +333,7 @@ void GLBackend::do_setResourceTextureTable(const Batch& batch, size_t paramOffse int GLBackend::ResourceStageState::findEmptyTextureSlot() const { // start from the end of the slots, try to find an empty one that can be used for (auto i = MAX_NUM_RESOURCE_TEXTURES - 1; i > 0; i--) { - if (!_textures[i]) { + if (!valid(_textures[i]._texture)) { return i; } } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp index b64804fe7c..61423bf970 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp @@ -25,15 +25,15 @@ static bool timeElapsed = false; #endif void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) { -#if !defined(USE_GLES) auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { PROFILE_RANGE_BEGIN(render_gpu_gl_detail, glquery->_profileRangeId, query->getName().c_str(), 0xFFFF7F00); ++_queryStage._rangeQueryDepth; - glGetInteger64v(GL_TIMESTAMP, (GLint64*)&glquery->_batchElapsedTime); + glquery->_batchElapsedTimeBegin = std::chrono::high_resolution_clock::now(); +#if !defined(USE_GLES) if (timeElapsed) { if (_queryStage._rangeQueryDepth <= MAX_RANGE_QUERY_DEPTH) { glBeginQuery(GL_TIME_ELAPSED, glquery->_endqo); @@ -41,17 +41,18 @@ void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) { } else { glQueryCounter(glquery->_beginqo, GL_TIMESTAMP); } +#endif + glquery->_rangeQueryDepth = _queryStage._rangeQueryDepth; (void)CHECK_GL_ERROR(); } -#endif } void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) { -#if !defined(USE_GLES) auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { +#if !defined(USE_GLES) if (timeElapsed) { if (_queryStage._rangeQueryDepth <= MAX_RANGE_QUERY_DEPTH) { glEndQuery(GL_TIME_ELAPSED); @@ -59,27 +60,26 @@ void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) { } else { glQueryCounter(glquery->_endqo, GL_TIMESTAMP); } +#endif --_queryStage._rangeQueryDepth; - GLint64 now; - glGetInteger64v(GL_TIMESTAMP, &now); - glquery->_batchElapsedTime = now - glquery->_batchElapsedTime; + auto duration_ns = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - glquery->_batchElapsedTimeBegin); + glquery->_batchElapsedTime = duration_ns.count(); PROFILE_RANGE_END(render_gpu_gl_detail, glquery->_profileRangeId); (void)CHECK_GL_ERROR(); } -#endif } void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) { -#if !defined(USE_GLES) auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { if (glquery->_rangeQueryDepth > MAX_RANGE_QUERY_DEPTH) { query->triggerReturnHandler(glquery->_result, glquery->_batchElapsedTime); } else { +#if !defined(USE_GLES) glGetQueryObjectui64v(glquery->_endqo, GL_QUERY_RESULT_AVAILABLE, &glquery->_result); if (glquery->_result == GL_TRUE) { if (timeElapsed) { @@ -92,10 +92,13 @@ void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) { } query->triggerReturnHandler(glquery->_result, glquery->_batchElapsedTime); } +#else + // gles3 is not supporting true time query returns just the batch elapsed time + query->triggerReturnHandler(0, glquery->_batchElapsedTime); +#endif (void)CHECK_GL_ERROR(); } } -#endif } void GLBackend::resetQueryStage() { diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp index ca4e328612..f4fb3fcf2c 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp @@ -66,7 +66,7 @@ GLTexture* GLBackend::syncGPUObject(const TexturePointer& texturePointer) { } void GLBackend::do_generateTextureMips(const Batch& batch, size_t paramOffset) { - TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); + const auto& resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); if (!resourceTexture) { return; } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h b/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h index 182014e764..6b7d3405f0 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBuffer.h @@ -49,6 +49,16 @@ public: } } + template + static GLuint getIdUnsynced(GLBackend& backend, const Buffer& buffer) { + GLBufferType* object = Backend::getGPUObject(buffer); + if (object) { + return object->_buffer; + } else { + return 0; + } + } + const GLuint& _buffer { _id }; const GLuint _size; const Stamp _stamp; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp index 1b479dceb8..a099e6e66a 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp @@ -24,7 +24,7 @@ GLPipeline* GLPipeline::sync(GLBackend& backend, const Pipeline& pipeline) { } // No object allocated yet, let's see if it's worth it... - ShaderPointer shader = pipeline.getProgram(); + const auto& shader = pipeline.getProgram(); // If this pipeline's shader has already failed to compile, don't try again if (shader->compilationHasFailed()) { @@ -37,7 +37,7 @@ GLPipeline* GLPipeline::sync(GLBackend& backend, const Pipeline& pipeline) { return nullptr; } - StatePointer state = pipeline.getState(); + const auto& state = pipeline.getState(); GLState* stateObject = GLState::sync(*state); if (stateObject == nullptr) { return nullptr; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLQuery.h b/libraries/gpu-gl-common/src/gpu/gl/GLQuery.h index 23b1f38621..11a4ef0c0a 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLQuery.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLQuery.h @@ -47,8 +47,9 @@ public: const GLuint& _endqo = { _id }; const GLuint _beginqo = { 0 }; - GLuint64 _result { (GLuint64)-1 }; - GLuint64 _batchElapsedTime { (GLuint64) 0 }; + GLuint64 _result { (GLuint64)0 }; + GLuint64 _batchElapsedTime{ (GLuint64)0 }; + std::chrono::high_resolution_clock::time_point _batchElapsedTimeBegin; uint64_t _profileRangeId { 0 }; uint32_t _rangeQueryDepth { 0 }; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp index eaee054b7d..f4e81448cc 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp @@ -405,6 +405,7 @@ bool GLTextureTransferEngineDefault::processActiveBufferQueue() { _activeTransferQueue.splice(_activeTransferQueue.end(), activeBufferQueue); } + Texture::KtxStorage::releaseOpenKtxFiles(); return true; } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index c6fbc43ae5..e5f7415107 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -134,6 +134,7 @@ protected: GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; + GLuint getBufferIDUnsynced(const Buffer& buffer) override; GLuint getResourceBufferID(const Buffer& buffer); GLBuffer* syncGPUObject(const Buffer& buffer) override; @@ -160,7 +161,7 @@ protected: void updateTransform(const Batch& batch) override; // Resource Stage - bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) override; + bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) override; void releaseResourceBuffer(uint32_t slot) override; // Output stage diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp index 62ade673b4..d5193f892a 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp @@ -83,6 +83,10 @@ GLuint GL41Backend::getBufferID(const Buffer& buffer) { return GL41Buffer::getId(*this, buffer); } +GLuint GL41Backend::getBufferIDUnsynced(const Buffer& buffer) { + return GL41Buffer::getIdUnsynced(*this, buffer); +} + GLuint GL41Backend::getResourceBufferID(const Buffer& buffer) { auto* object = GL41Buffer::sync(*this, buffer); if (object) { @@ -96,7 +100,7 @@ GLBuffer* GL41Backend::syncGPUObject(const Buffer& buffer) { return GL41Buffer::sync(*this, buffer); } -bool GL41Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { +bool GL41Backend::bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) { GLuint texBuffer = GL41Backend::getResourceBufferID((*buffer)); if (texBuffer) { glActiveTexture(GL_TEXTURE0 + GL41Backend::RESOURCE_BUFFER_SLOT0_TEX_UNIT + slot); @@ -104,7 +108,7 @@ bool GL41Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { (void)CHECK_GL_ERROR(); - _resource._buffers[slot] = buffer; + assign(_resource._buffers[slot], buffer); return true; } @@ -113,10 +117,11 @@ bool GL41Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { } void GL41Backend::releaseResourceBuffer(uint32_t slot) { - auto& buf = _resource._buffers[slot]; - if (buf) { + auto& bufferReference = _resource._buffers[slot]; + auto buffer = acquire(bufferReference); + if (buffer) { glActiveTexture(GL_TEXTURE0 + GL41Backend::RESOURCE_BUFFER_SLOT0_TEX_UNIT + slot); glBindTexture(GL_TEXTURE_BUFFER, 0); - buf.reset(); + reset(bufferReference); } } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp index 9dcb08f0b7..2b985c122e 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp @@ -35,14 +35,15 @@ void GL41Backend::updateInput() { if (_input._invalidFormat || _input._invalidBuffers.any()) { + auto format = acquire(_input._format); if (_input._invalidFormat) { InputStageState::ActivationCache newActivation; _stats._ISNumFormatChanges++; // Check expected activation - if (_input._format) { - for (auto& it : _input._format->getAttributes()) { + if (format) { + for (auto& it : format->getAttributes()) { const Stream::Attribute& attrib = (it).second; uint8_t locationCount = attrib._element.getLocationCount(); for (int i = 0; i < locationCount; ++i) { @@ -69,17 +70,18 @@ void GL41Backend::updateInput() { } // now we need to bind the buffers and assign the attrib pointers - if (_input._format) { + if (format) { bool hasColorAttribute{ false }; - const Buffers& buffers = _input._buffers; - const Offsets& offsets = _input._bufferOffsets; - const Offsets& strides = _input._bufferStrides; - - const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); - auto& inputChannels = _input._format->getChannels(); - _stats._ISNumInputBufferChanges++; + const auto& buffers = _input._buffers; + const auto& offsets = _input._bufferOffsets; + const auto& strides = _input._bufferStrides; + const auto& attributes = format->getAttributes(); + const auto& inputChannels = format->getChannels(); + int numInvalids = (int)_input._invalidBuffers.count(); + _stats._ISNumInputBufferChanges += numInvalids; + GLuint boundVBO = 0; for (auto& channelIt : inputChannels) { const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp index 8a67ff9619..46f91fdc15 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp @@ -57,7 +57,7 @@ void GL41Backend::postLinkProgram(ShaderObject& programObject, const Shader& pro const auto resourceBufferUniforms = ::gl::Uniform::loadByName(glprogram, program.getResourceBuffers().getNames()); for (const auto& resourceBuffer : resourceBufferUniforms) { const auto& targetBinding = expectedResourceBuffers.at(resourceBuffer.name); - glProgramUniform1i(glprogram, resourceBuffer.binding, targetBinding); + glProgramUniform1i(glprogram, resourceBuffer.binding, targetBinding + GL41Backend::RESOURCE_BUFFER_SLOT0_TEX_UNIT); } } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 658bea2a3e..30656b47c7 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -235,6 +235,7 @@ protected: GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; + GLuint getBufferIDUnsynced(const Buffer& buffer) override; GLBuffer* syncGPUObject(const Buffer& buffer) override; GLTexture* syncGPUObject(const TexturePointer& texture) override; @@ -261,7 +262,7 @@ protected: void updateTransform(const Batch& batch) override; // Resource Stage - bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) override; + bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) override; void releaseResourceBuffer(uint32_t slot) override; // Output stage diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp index 2afbea3876..cb0591c31c 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp @@ -51,19 +51,23 @@ GLuint GL45Backend::getBufferID(const Buffer& buffer) { return GL45Buffer::getId(*this, buffer); } +GLuint GL45Backend::getBufferIDUnsynced(const Buffer& buffer) { + return GL45Buffer::getIdUnsynced(*this, buffer); +} + GLBuffer* GL45Backend::syncGPUObject(const Buffer& buffer) { return GL45Buffer::sync(*this, buffer); } -bool GL45Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { +bool GL45Backend::bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) { GLBuffer* object = syncGPUObject((*buffer)); if (object) { glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, object->_id); (void)CHECK_GL_ERROR(); - _resource._buffers[slot] = buffer; + assign(_resource._buffers[slot], buffer); return true; } @@ -72,11 +76,10 @@ bool GL45Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { } void GL45Backend::releaseResourceBuffer(uint32_t slot) { - auto& buf = _resource._buffers[slot]; - if (buf) { + auto& bufferReference = _resource._buffers[slot]; + auto buffer = acquire(bufferReference); + if (buffer) { glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, 0); - buf.reset(); + reset(bufferReference); } } - - diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp index cc3e609bda..5285e62d3e 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp @@ -28,7 +28,7 @@ void GL45Backend::resetInputStage() { void GL45Backend::updateInput() { bool isStereoNow = isStereo(); - // track stereo state change potentially happening wihtout changing the input format + // track stereo state change potentially happening without changing the input format // this is a rare case requesting to invalid the format #ifdef GPU_STEREO_DRAWCALL_INSTANCED _input._invalidFormat |= (isStereoNow != _input._lastUpdateStereoState); @@ -39,13 +39,14 @@ void GL45Backend::updateInput() { InputStageState::ActivationCache newActivation; // Assign the vertex format required - if (_input._format) { + auto format = acquire(_input._format); + if (format) { bool hasColorAttribute{ false }; _input._attribBindingBuffers.reset(); - const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); - auto& inputChannels = _input._format->getChannels(); + const auto& attributes = format->getAttributes(); + const auto& inputChannels = format->getChannels(); for (auto& channelIt : inputChannels) { auto bufferChannelNum = (channelIt).first; const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second; @@ -132,9 +133,18 @@ void GL45Backend::updateInput() { auto offset = _input._bufferOffsets.data(); auto stride = _input._bufferStrides.data(); - for (GLuint buffer = 0; buffer < _input._buffers.size(); buffer++, vbo++, offset++, stride++) { + // Profile the count of buffers to update and use it to short cut the for loop + int numInvalids = (int) _input._invalidBuffers.count(); + _stats._ISNumInputBufferChanges += numInvalids; + + auto numBuffers = _input._buffers.size(); + for (GLuint buffer = 0; buffer < numBuffers; buffer++, vbo++, offset++, stride++) { if (_input._invalidBuffers.test(buffer)) { glBindVertexBuffer(buffer, (*vbo), (*offset), (GLsizei)(*stride)); + numInvalids--; + if (numInvalids <= 0) { + break; + } } } diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h index 785f4c3ef9..56ae41da31 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h @@ -130,6 +130,7 @@ protected: GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; + GLuint getBufferIDUnsynced(const Buffer& buffer) override; GLuint getResourceBufferID(const Buffer& buffer); GLBuffer* syncGPUObject(const Buffer& buffer) override; @@ -156,7 +157,7 @@ protected: void updateTransform(const Batch& batch) override; // Resource Stage - bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) override; + bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) override; void releaseResourceBuffer(uint32_t slot) override; // Output stage diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp index 17fdad8377..5e4da4d1fe 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp @@ -64,18 +64,22 @@ GLuint GLESBackend::getBufferID(const Buffer& buffer) { return GLESBuffer::getId(*this, buffer); } +GLuint GLESBackend::getBufferIDUnsynced(const Buffer& buffer) { + return GLESBuffer::getIdUnsynced(*this, buffer); +} + GLBuffer* GLESBackend::syncGPUObject(const Buffer& buffer) { return GLESBuffer::sync(*this, buffer); } -bool GLESBackend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { +bool GLESBackend::bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) { GLBuffer* object = syncGPUObject((*buffer)); if (object) { glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, object->_id); (void)CHECK_GL_ERROR(); - _resource._buffers[slot] = buffer; + assign(_resource._buffers[slot], buffer); return true; } @@ -84,10 +88,10 @@ bool GLESBackend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { } void GLESBackend::releaseResourceBuffer(uint32_t slot) { - auto& buf = _resource._buffers[slot]; - if (buf) { + auto& bufferReference = _resource._buffers[slot]; + if (valid(bufferReference)) { glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, 0); - buf.reset(); + reset(bufferReference); } } diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index b6714e2f1a..745f1d1845 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -498,7 +498,7 @@ void Batch::setupNamedCalls(const std::string& instanceName, NamedBatchData::Fun captureNamedDrawCallInfo(instanceName); } -BufferPointer Batch::getNamedBuffer(const std::string& instanceName, uint8_t index) { +const BufferPointer& Batch::getNamedBuffer(const std::string& instanceName, uint8_t index) { NamedBatchData& instance = _namedData[instanceName]; if (instance.buffers.size() <= index) { instance.buffers.resize(index + 1); diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index bcbfe0616d..8e607a189e 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -119,7 +119,7 @@ public: void multiDrawIndexedIndirect(uint32 numCommands, Primitive primitiveType); void setupNamedCalls(const std::string& instanceName, NamedBatchData::Function function); - BufferPointer getNamedBuffer(const std::string& instanceName, uint8_t index = 0); + const BufferPointer& getNamedBuffer(const std::string& instanceName, uint8_t index = 0); // Input Stage // InputFormat @@ -417,10 +417,7 @@ public: } const Data& get(uint32 offset) const { - if (offset >= _items.size()) { - static const Data EMPTY; - return EMPTY; - } + assert((offset < _items.size())); return (_items.data() + offset)->_data; } diff --git a/libraries/gpu/src/gpu/Color.slh b/libraries/gpu/src/gpu/Color.slh index 384b8bd329..16eb7487ed 100644 --- a/libraries/gpu/src/gpu/Color.slh +++ b/libraries/gpu/src/gpu/Color.slh @@ -31,29 +31,29 @@ vec4 color_sRGBAToLinear(vec4 srgba) { } vec3 color_LinearToYCoCg(vec3 rgb) { - // Y = R/4 + G/2 + B/4 - // Co = R/2 - B/2 - // Cg = -R/4 + G/2 - B/4 - return vec3( - rgb.x/4.0 + rgb.y/2.0 + rgb.z/4.0, - rgb.x/2.0 - rgb.z/2.0, - -rgb.x/4.0 + rgb.y/2.0 - rgb.z/4.0 - ); + // Y = R/4 + G/2 + B/4 + // Co = R/2 - B/2 + // Cg = -R/4 + G/2 - B/4 + return vec3( + rgb.x/4.0 + rgb.y/2.0 + rgb.z/4.0, + rgb.x/2.0 - rgb.z/2.0, + -rgb.x/4.0 + rgb.y/2.0 - rgb.z/4.0 + ); } vec3 color_YCoCgToUnclampedLinear(vec3 ycocg) { - // R = Y + Co - Cg - // G = Y + Cg - // B = Y - Co - Cg - return vec3( - ycocg.x + ycocg.y - ycocg.z, - ycocg.x + ycocg.z, - ycocg.x - ycocg.y - ycocg.z - ); + // R = Y + Co - Cg + // G = Y + Cg + // B = Y - Co - Cg + return vec3( + ycocg.x + ycocg.y - ycocg.z, + ycocg.x + ycocg.z, + ycocg.x - ycocg.y - ycocg.z + ); } vec3 color_YCoCgToLinear(vec3 ycocg) { - return clamp(color_YCoCgToUnclampedLinear(ycocg), vec3(0.0), vec3(1.0)); + return clamp(color_YCoCgToUnclampedLinear(ycocg), vec3(0.0), vec3(1.0)); } <@func declareColorWheel()@> diff --git a/libraries/gpu/src/gpu/DrawColor.slf b/libraries/gpu/src/gpu/DrawColor.slf index 2540d56d69..fdea26fa68 100644 --- a/libraries/gpu/src/gpu/DrawColor.slf +++ b/libraries/gpu/src/gpu/DrawColor.slf @@ -13,12 +13,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/ShaderConstants.h@> +struct DrawColorParams { + vec4 color; +}; -layout(location=GPU_UNIFORM_COLOR) uniform vec4 color; +layout(binding=0) uniform drawColorParamsBuffer { + DrawColorParams params; +}; layout(location=0) out vec4 outFragColor; void main(void) { - outFragColor = color; + outFragColor = params.color; } diff --git a/libraries/gpu/src/gpu/DrawColoredTexture.slf b/libraries/gpu/src/gpu/DrawColoredTexture.slf index 632bf18391..0fe3707b1c 100755 --- a/libraries/gpu/src/gpu/DrawColoredTexture.slf +++ b/libraries/gpu/src/gpu/DrawColoredTexture.slf @@ -13,14 +13,19 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/ShaderConstants.h@> - layout(binding=0) uniform sampler2D colorMap; -layout(location=GPU_UNIFORM_COLOR) uniform vec4 color; + +struct DrawColorParams { + vec4 color; +}; + +layout(binding=0) uniform drawColorParams { + DrawColorParams params; +}; layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; void main(void) { - outFragColor = texture(colorMap, varTexCoord0) * color; + outFragColor = texture(colorMap, varTexCoord0) * params.color; } diff --git a/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv b/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv index d401fc40c2..8849cb494a 100755 --- a/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv +++ b/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv @@ -21,7 +21,13 @@ <$declareStandardTransform()$> -layout(location=GPU_UNIFORM_TEXCOORD_RECT) uniform vec4 texcoordRect; +struct TexCoordRectParams { + vec4 texcoordRect; +}; + +layout(binding=0) uniform texcoordRectBuffer { + TexCoordRectParams params; +}; layout(location=0) out vec2 varTexCoord0; @@ -39,5 +45,5 @@ void main(void) { TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, pos, gl_Position)$> - varTexCoord0 = ((pos.xy + 1.0) * 0.5) * texcoordRect.zw + texcoordRect.xy; + varTexCoord0 = ((pos.xy + 1.0) * 0.5) * params.texcoordRect.zw + params.texcoordRect.xy; } diff --git a/libraries/gpu/src/gpu/Framebuffer.cpp b/libraries/gpu/src/gpu/Framebuffer.cpp index 8bb9be4a76..e88d986da6 100755 --- a/libraries/gpu/src/gpu/Framebuffer.cpp +++ b/libraries/gpu/src/gpu/Framebuffer.cpp @@ -203,11 +203,12 @@ uint32 Framebuffer::getNumRenderBuffers() const { return nb; } -TexturePointer Framebuffer::getRenderBuffer(uint32 slot) const { +const TexturePointer& Framebuffer::getRenderBuffer(uint32 slot) const { + static const TexturePointer EMPTY; if (!isSwapchain() && (slot < getMaxNumRenderBuffers())) { return _renderBuffers[slot]._texture; } else { - return TexturePointer(); + return EMPTY; } } @@ -297,9 +298,10 @@ bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const For return false; } -TexturePointer Framebuffer::getDepthStencilBuffer() const { +const TexturePointer& Framebuffer::getDepthStencilBuffer() const { + static const TexturePointer EMPTY; if (isSwapchain()) { - return TexturePointer(); + return EMPTY; } else { return _depthStencilBuffer._texture; } diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h index fbbec50a28..44e945883f 100755 --- a/libraries/gpu/src/gpu/Framebuffer.h +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -95,7 +95,7 @@ public: static Framebuffer* createShadowmap(uint16 width); bool isSwapchain() const; - SwapchainPointer getSwapchain() const { return _swapchain; } + const SwapchainPointer& getSwapchain() const { return _swapchain; } uint32 getFrameCount() const; @@ -105,13 +105,13 @@ public: const TextureViews& getRenderBuffers() const { return _renderBuffers; } int32 setRenderBuffer(uint32 slot, const TexturePointer& texture, uint32 subresource = 0); - TexturePointer getRenderBuffer(uint32 slot) const; + const TexturePointer& getRenderBuffer(uint32 slot) const; uint32 getRenderBufferSubresource(uint32 slot) const; bool setDepthBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0); bool setStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0); bool setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0); - TexturePointer getDepthStencilBuffer() const; + const TexturePointer& getDepthStencilBuffer() const; uint32 getDepthStencilBufferSubresource() const; Format getDepthStencilBufferFormat() const; diff --git a/libraries/gpu/src/gpu/Noise.slh b/libraries/gpu/src/gpu/Noise.slh index b390945957..d300e71ba9 100644 --- a/libraries/gpu/src/gpu/Noise.slh +++ b/libraries/gpu/src/gpu/Noise.slh @@ -284,14 +284,14 @@ float hifi_noise(in vec2 x) { // https://www.shadertoy.com/view/MdX3Rr // https://en.wikipedia.org/wiki/Fractional_Brownian_motion float hifi_fbm(in vec2 p) { - const mat2 m2 = mat2(0.8, -0.6, 0.6, 0.8); - float f = 0.0; - f += 0.5000 * hifi_noise(p); p = m2 * p * 2.02; - f += 0.2500 * hifi_noise(p); p = m2 * p * 2.03; - f += 0.1250 * hifi_noise(p); p = m2 * p * 2.01; - f += 0.0625 * hifi_noise(p); + const mat2 m2 = mat2(0.8, -0.6, 0.6, 0.8); + float f = 0.0; + f += 0.5000 * hifi_noise(p); p = m2 * p * 2.02; + f += 0.2500 * hifi_noise(p); p = m2 * p * 2.03; + f += 0.1250 * hifi_noise(p); p = m2 * p * 2.01; + f += 0.0625 * hifi_noise(p); - return f / 0.9375; + return f / 0.9375; } <@endif@> \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Paint.slh b/libraries/gpu/src/gpu/Paint.slh index 5f49b20b30..54a1214e12 100644 --- a/libraries/gpu/src/gpu/Paint.slh +++ b/libraries/gpu/src/gpu/Paint.slh @@ -17,7 +17,7 @@ float paintStripe(float value, float offset, float scale, float edge) { float width = fwidth(value); float normalizedWidth = width * scale; - float x0 = (value + offset) * scale - normalizedWidth / 2; + float x0 = (value + offset) * scale - normalizedWidth / 2.0; float x1 = x0 + normalizedWidth; float balance = 1.0 - edge; diff --git a/libraries/gpu/src/gpu/ShaderConstants.h b/libraries/gpu/src/gpu/ShaderConstants.h index dc5879e7ad..13dfd1be9c 100644 --- a/libraries/gpu/src/gpu/ShaderConstants.h +++ b/libraries/gpu/src/gpu/ShaderConstants.h @@ -40,8 +40,6 @@ // OSX seems to have an issue using 14 as an attribute location for passing from the vertex to the fragment shader #define GPU_ATTR_V2F_STEREO_SIDE 8 -#define GPU_UNIFORM_COLOR 101 -#define GPU_UNIFORM_TEXCOORD_RECT 102 #define GPU_UNIFORM_EXTRA0 110 #define GPU_UNIFORM_EXTRA1 111 #define GPU_UNIFORM_EXTRA2 112 @@ -98,8 +96,6 @@ enum Attribute { namespace uniform { enum Uniform { - Color = GPU_UNIFORM_COLOR, - TexCoordRect = GPU_UNIFORM_TEXCOORD_RECT, Extra0 = GPU_UNIFORM_EXTRA0, Extra1 = GPU_UNIFORM_EXTRA1, Extra2 = GPU_UNIFORM_EXTRA2, diff --git a/libraries/gpu/src/gpu/Stream.h b/libraries/gpu/src/gpu/Stream.h index 0def1ab201..2e1ed1d83c 100644 --- a/libraries/gpu/src/gpu/Stream.h +++ b/libraries/gpu/src/gpu/Stream.h @@ -152,6 +152,8 @@ public: BufferStream makeRangedStream(uint32 offset, uint32 count = -1) const; + BufferStream& operator = (const BufferStream& src) = default; + protected: Buffers _buffers; Offsets _offsets; diff --git a/libraries/gpu/src/gpu/TransformCamera_shared.slh b/libraries/gpu/src/gpu/TransformCamera_shared.slh index 37521d8201..e4a0f8c2cc 100644 --- a/libraries/gpu/src/gpu/TransformCamera_shared.slh +++ b/libraries/gpu/src/gpu/TransformCamera_shared.slh @@ -2,11 +2,11 @@ #ifdef __cplusplus # define _MAT4 Mat4 # define _VEC4 Vec4 -# define _MUTABLE mutable +# define _MUTABLE mutable #else # define _MAT4 mat4 # define _VEC4 vec4 -# define _MUTABLE +# define _MUTABLE #endif struct _TransformCamera { diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.cpp b/libraries/graphics/src/graphics/BufferViewHelpers.cpp index 5c7e6ff892..2fd0d90da3 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.cpp +++ b/libraries/graphics/src/graphics/BufferViewHelpers.cpp @@ -20,9 +20,6 @@ #include #include -#include -#include - namespace glm { using hvec2 = glm::tvec2; using hvec4 = glm::tvec4; @@ -62,32 +59,6 @@ namespace { } } -void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { - auto absNormal = glm::abs(normal); - auto absTangent = glm::abs(tangent); - normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z)); - tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z)); - normal = glm::clamp(normal, -1.0f, 1.0f); - tangent = glm::clamp(tangent, -1.0f, 1.0f); - normal *= 511.0f; - tangent *= 511.0f; - normal = glm::round(normal); - tangent = glm::round(tangent); - - glm::detail::i10i10i10i2 normalStruct; - glm::detail::i10i10i10i2 tangentStruct; - normalStruct.data.x = int(normal.x); - normalStruct.data.y = int(normal.y); - normalStruct.data.z = int(normal.z); - normalStruct.data.w = 0; - tangentStruct.data.x = int(tangent.x); - tangentStruct.data.y = int(tangent.y); - tangentStruct.data.z = int(tangent.z); - tangentStruct.data.w = 0; - packedNormal = normalStruct.pack; - packedTangent = tangentStruct.pack; -} - template glm::uint32 forEachGlmVec(const gpu::BufferView& view, std::function func) { QVector result; diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.h b/libraries/graphics/src/graphics/BufferViewHelpers.h index f877341d50..026e7b53a3 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.h +++ b/libraries/graphics/src/graphics/BufferViewHelpers.h @@ -9,8 +9,11 @@ #include #include #include +#include +#include #include "GpuHelpers.h" +#include "GLMHelpers.h" namespace graphics { class Mesh; @@ -44,7 +47,29 @@ namespace buffer_helpers { gpu::BufferView clone(const gpu::BufferView& input); gpu::BufferView resized(const gpu::BufferView& input, glm::uint32 numElements); - void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent); + inline void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { + auto absNormal = glm::abs(normal); + auto absTangent = glm::abs(tangent); + normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z)); + tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z)); + normal = glm::clamp(normal, -1.0f, 1.0f); + tangent = glm::clamp(tangent, -1.0f, 1.0f); + normal *= 511.0f; + tangent *= 511.0f; + + glm::detail::i10i10i10i2 normalStruct; + glm::detail::i10i10i10i2 tangentStruct; + normalStruct.data.x = fastLrintf(normal.x); + normalStruct.data.y = fastLrintf(normal.y); + normalStruct.data.z = fastLrintf(normal.z); + normalStruct.data.w = 0; + tangentStruct.data.x = fastLrintf(tangent.x); + tangentStruct.data.y = fastLrintf(tangent.y); + tangentStruct.data.z = fastLrintf(tangent.z); + tangentStruct.data.w = 0; + packedNormal = normalStruct.pack; + packedTangent = tangentStruct.pack; + } namespace mesh { glm::uint32 forEachVertex(const graphics::MeshPointer& mesh, std::function func); diff --git a/libraries/graphics/src/graphics/Geometry.cpp b/libraries/graphics/src/graphics/Geometry.cpp index d43c773249..a983ba07b4 100755 --- a/libraries/graphics/src/graphics/Geometry.cpp +++ b/libraries/graphics/src/graphics/Geometry.cpp @@ -32,6 +32,15 @@ Mesh::Mesh(const Mesh& mesh) : Mesh::~Mesh() { } +void Mesh::setVertexFormatAndStream(const gpu::Stream::FormatPointer& vf, const gpu::BufferStreamPointer& vbs) { + _vertexFormat = vf; + _vertexStream = (*vbs); + + auto attrib = _vertexFormat->getAttribute(gpu::Stream::POSITION); + _vertexBuffer = BufferView(vbs->getBuffers()[attrib._channel], vbs->getOffsets()[attrib._channel], vbs->getBuffers()[attrib._channel]->getSize(), + (gpu::uint16) vbs->getStrides()[attrib._channel], attrib._element); +} + void Mesh::setVertexBuffer(const BufferView& buffer) { _vertexBuffer = buffer; evalVertexFormat(); @@ -107,11 +116,10 @@ Box Mesh::evalPartBound(int partNum) const { index += part._startIndex; auto endIndex = index; endIndex += part._numIndices; - auto vertices = &_vertexBuffer.get(part._baseVertex); for (;index != endIndex; index++) { // skip primitive restart indices if ((*index) != PRIMITIVE_RESTART_INDEX) { - box += vertices[(*index)]; + box += _vertexBuffer.get(part._baseVertex + (*index)); } } } @@ -128,11 +136,10 @@ Box Mesh::evalPartsBound(int partStart, int partEnd) const { Box partBound; auto index = _indexBuffer.cbegin() + (*part)._startIndex; auto endIndex = index + (*part)._numIndices; - auto vertices = &_vertexBuffer.get((*part)._baseVertex); for (;index != endIndex; index++) { // skip primitive restart indices if ((*index) != (uint) PRIMITIVE_RESTART_INDEX) { - partBound += vertices[(*index)]; + partBound += _vertexBuffer.get((*part)._baseVertex + (*index)); } } diff --git a/libraries/graphics/src/graphics/Geometry.h b/libraries/graphics/src/graphics/Geometry.h index eddfdbd1b6..20018ba71b 100755 --- a/libraries/graphics/src/graphics/Geometry.h +++ b/libraries/graphics/src/graphics/Geometry.h @@ -59,6 +59,9 @@ public: void removeAttribute(Slot slot); const BufferView getAttributeBuffer(int attrib) const; + // Force vertex stream and Vertex format + void setVertexFormatAndStream(const gpu::Stream::FormatPointer& vf, const gpu::BufferStreamPointer& vbs); + // Stream format const gpu::Stream::FormatPointer getVertexFormat() const { return _vertexFormat; } diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index 650c9675a7..ddd51e9b9b 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -302,6 +302,8 @@ controller::Input::NamedVector KeyboardMouseDevice::InputDevice::getAvailableInp availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString())); availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Tab), QKeySequence(Qt::Key_Tab).toString())); availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Control), "Control")); + availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Delete), "Delete")); + availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Backspace), QKeySequence(Qt::Key_Backspace).toString())); availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseButton")); availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseButton")); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 5b3196a2bf..d99c0020da 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -538,7 +538,6 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas QNetworkReply* requestReply = networkAccessManager.post(request, postData); connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished); - connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); } void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) { @@ -633,12 +632,6 @@ void AccountManager::requestAccessTokenFinished() { } } -void AccountManager::requestAccessTokenError(QNetworkReply::NetworkError error) { - // TODO: error handling - qCDebug(networking) << "AccountManager: failed to fetch access token - " << error; - emit loginFailed(); -} - void AccountManager::refreshAccessTokenFinished() { QNetworkReply* requestReply = reinterpret_cast(sender()); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index a79b69fe2b..f3b81cf1c9 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -96,6 +96,8 @@ public: QUrl getMetaverseServerURL() { return NetworkingConstants::METAVERSE_SERVER_URL(); } + void removeAccountFromFile(); + public slots: void requestAccessToken(const QString& login, const QString& password); void requestAccessTokenWithSteam(QByteArray authSessionTicket); @@ -104,7 +106,6 @@ public slots: void requestAccessTokenFinished(); void refreshAccessTokenFinished(); void requestProfileFinished(); - void requestAccessTokenError(QNetworkReply::NetworkError error); void refreshAccessTokenError(QNetworkReply::NetworkError error); void requestProfileError(QNetworkReply::NetworkError error); void logout(); @@ -133,7 +134,6 @@ private: void operator=(AccountManager const& other) = delete; void persistAccountToFile(); - void removeAccountFromFile(); void passSuccessToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 00e552af89..8085039b02 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -31,6 +31,7 @@ #include "udt/PacketHeaders.h" const QString DEFAULT_HIFI_ADDRESS = "file:///~/serverless/tutorial.json"; +const QString REDIRECT_HIFI_ADDRESS = "file:///~/serverless/redirect.json"; const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; @@ -111,6 +112,9 @@ QUrl AddressManager::currentFacingPublicAddress() const { return shareableAddress; } +QUrl AddressManager::lastAddress() const { + return _lastVisitedURL; +} void AddressManager::loadSettings(const QString& lookupString) { #if defined(USE_GLES) && defined(Q_OS_WIN) @@ -247,9 +251,12 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_USER, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // in case we're failing to connect to where we thought this user was // store their username as previous lookup so we can refresh their location via API - _previousLookup = lookupUrl; + _previousAPILookup = lookupUrl; } else { // we're assuming this is either a network address or global place name // check if it is a network address first @@ -259,8 +266,11 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_NETWORK_ADDRESS, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // a network address lookup clears the previous lookup since we don't expect to re-attempt it - _previousLookup.clear(); + _previousAPILookup.clear(); // If the host changed then we have already saved to history if (hostChanged) { @@ -278,8 +288,11 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { } else if (handleDomainID(lookupUrl.host())){ UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_DOMAIN_ID, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info - _previousLookup = lookupUrl; + _previousAPILookup = lookupUrl; // no place name - this is probably a domain ID // try to look up the domain ID on the metaverse API @@ -287,8 +300,11 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { } else { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_PLACE, lookupUrl.toString()); + // save the last visited domain URL. + _lastVisitedURL = lookupUrl; + // store this place name as the previous lookup in case we fail to connect and want to refresh API info - _previousLookup = lookupUrl; + _previousAPILookup = lookupUrl; // wasn't an address - lookup the place name // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after @@ -302,7 +318,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { qCDebug(networking) << "Going to relative path" << lookupUrl.path(); // a path lookup clears the previous lookup since we don't expect to re-attempt it - _previousLookup.clear(); + _previousAPILookup.clear(); // if this is a relative path then handle it as a relative viewpoint handlePath(lookupUrl.path(), trigger, true); @@ -314,7 +330,10 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // be loaded over http(s) // lookupUrl.scheme() == URL_SCHEME_HTTP || // lookupUrl.scheme() == URL_SCHEME_HTTPS || - _previousLookup.clear(); + // TODO once a file can return a connection refusal if there were to be some kind of load error, we'd + // need to store the previous domain tried in _lastVisitedURL. For now , do not store it. + + _previousAPILookup.clear(); _shareablePlaceName.clear(); setDomainInfo(lookupUrl, trigger); emit lookupResultsFinished(); @@ -381,7 +400,7 @@ void AddressManager::handleAPIResponse(QNetworkReply* requestReply) { QJsonObject dataObject = responseObject["data"].toObject(); // Lookup succeeded, don't keep re-trying it (especially on server restarts) - _previousLookup.clear(); + _previousAPILookup.clear(); if (!dataObject.isEmpty()) { goToAddressFromObject(dataObject.toVariantMap(), requestReply); @@ -547,7 +566,7 @@ void AddressManager::handleAPIError(QNetworkReply* errorReply) { if (errorReply->error() == QNetworkReply::ContentNotFoundError) { // if this is a lookup that has no result, don't keep re-trying it - _previousLookup.clear(); + _previousAPILookup.clear(); emit lookupResultIsNotFound(); } @@ -709,7 +728,6 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should // We use _newHostLookupPath to determine if the client has already stored its last address // before moving to a new host thanks to the information in the same lookup URL. - if (definitelyPathOnly || (!pathString.isEmpty() && pathString != _newHostLookupPath) || trigger == Back || trigger == Forward) { addCurrentAddressToHistory(trigger); @@ -798,8 +816,10 @@ bool AddressManager::setDomainInfo(const QUrl& domainURL, LookupTrigger trigger) const QString hostname = domainURL.host(); quint16 port = domainURL.port(); bool emitHostChanged { false }; + // Check if domain handler is in error state. always emit host changed if true. + bool isInErrorState = DependencyManager::get()->getDomainHandler().isInErrorState(); - if (domainURL != _domainURL) { + if (domainURL != _domainURL || isInErrorState) { addCurrentAddressToHistory(trigger); emitHostChanged = true; } @@ -843,8 +863,8 @@ void AddressManager::goToUser(const QString& username, bool shouldMatchOrientati void AddressManager::refreshPreviousLookup() { // if we have a non-empty previous lookup, fire it again now (but don't re-store it in the history) - if (!_previousLookup.isEmpty()) { - handleUrl(_previousLookup, LookupTrigger::AttemptedRefresh); + if (!_previousAPILookup.isEmpty()) { + handleUrl(_previousAPILookup, LookupTrigger::AttemptedRefresh); } else { handleUrl(currentAddress(), LookupTrigger::AttemptedRefresh); } diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 37b85a9acd..17041a5fd7 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -23,6 +23,7 @@ #include "AccountManager.h" extern const QString DEFAULT_HIFI_ADDRESS; +extern const QString REDIRECT_HIFI_ADDRESS; const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost"; const QString INDEX_PATH = "/"; @@ -55,7 +56,6 @@ const QString GET_PLACE = "/api/v1/places/%1"; * Read-only. * @property {boolean} isConnected - true if you're connected to the domain in your current href * metaverse address, otherwise false. - * Read-only. * @property {string} pathname - The location and orientation in your current href metaverse address * (e.g., "/15,-10,26/0,0,0,1"). * Read-only. @@ -164,6 +164,8 @@ public: QString currentPath(bool withOrientation = true) const; QString currentFacingPath() const; + QUrl lastAddress() const; + const QUuid& getRootPlaceID() const { return _rootPlaceID; } QString getPlaceName() const; QString getDomainID() const; @@ -191,7 +193,7 @@ public slots: * Helps ensure that user's location history is correctly maintained. */ void handleLookupString(const QString& lookupString, bool fromSuggestions = false); - + /**jsdoc * Go to a position and orientation resulting from a lookup for a named path in the domain (set in the domain server's * settings). @@ -226,8 +228,9 @@ public slots: * @param {location.LookupTrigger} trigger=StartupFromSettings - The reason for the function call. Helps ensure that user's * location history is correctly maintained. */ - void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); } - + void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { + handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); } + /**jsdoc * Go to the default "welcome" metaverse address. * @function location.goToEntry @@ -245,6 +248,12 @@ public slots: */ void goToUser(const QString& username, bool shouldMatchOrientation = true); + /**jsdoc + * Go to the last address tried. This will be the last URL tried from location.handleLookupString + * @function location.goToLastAddress + */ + void goToLastAddress() { handleUrl(_lastVisitedURL, LookupTrigger::AttemptedRefresh); } + /**jsdoc * Refresh the current address, e.g., after connecting to a domain in order to position the user to the desired location. * @function location.refreshPreviousLookup @@ -423,7 +432,7 @@ private slots: void handleShareableNameAPIResponse(QNetworkReply* requestReply); private: - void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply* reply); + void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply* reply); // Set host and port, and return `true` if it was changed. bool setHost(const QString& host, LookupTrigger trigger, quint16 port = 0); @@ -446,6 +455,7 @@ private: void addCurrentAddressToHistory(LookupTrigger trigger); QUrl _domainURL; + QUrl _lastVisitedURL; QUuid _rootPlaceID; PositionGetter _positionGetter; @@ -459,7 +469,7 @@ private: QString _newHostLookupPath; - QUrl _previousLookup; + QUrl _previousAPILookup; }; -#endif // hifi_AddressManager_h +#endif // hifi_AddressManager_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 39c8b5b1a1..df34a1fb59 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -15,6 +15,10 @@ #include +#include + +#include + #include #include @@ -55,6 +59,9 @@ DomainHandler::DomainHandler(QObject* parent) : // stop the refresh timer if we connect to a domain connect(this, &DomainHandler::connectedToDomain, &_apiRefreshTimer, &QTimer::stop); + + // stop the refresh timer if redirected to the error domain + connect(this, &DomainHandler::redirectToErrorDomainURL, &_apiRefreshTimer, &QTimer::stop); } void DomainHandler::disconnect() { @@ -106,13 +113,16 @@ void DomainHandler::softReset() { QMetaObject::invokeMethod(&_settingsTimer, "stop"); // restart the API refresh timer in case we fail to connect and need to refresh information - QMetaObject::invokeMethod(&_apiRefreshTimer, "start"); + if (!_isInErrorState) { + QMetaObject::invokeMethod(&_apiRefreshTimer, "start"); + } } void DomainHandler::hardReset() { emit resetting(); softReset(); + _isInErrorState = false; qCDebug(networking) << "Hard reset in NodeList DomainHandler."; _pendingDomainID = QUuid(); @@ -128,6 +138,23 @@ void DomainHandler::hardReset() { _pendingPath.clear(); } +bool DomainHandler::getInterstitialModeEnabled() const { + return _interstitialModeSettingLock.resultWithReadLock([&] { + return _enableInterstitialMode.get(); + }); +} + +void DomainHandler::setInterstitialModeEnabled(bool enableInterstitialMode) { + _interstitialModeSettingLock.withWriteLock([&] { + _enableInterstitialMode.set(enableInterstitialMode); + }); +} + +void DomainHandler::setErrorDomainURL(const QUrl& url) { + _errorDomainURL = url; + return; +} + void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) { if (_sockAddr != sockAddr) { // we should reset on a sockAddr change @@ -171,7 +198,8 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { domainPort = DEFAULT_DOMAIN_SERVER_PORT; } - if (_domainURL != domainURL || _sockAddr.getPort() != domainPort) { + // if it's in the error state, reset and try again. + if ((_domainURL != domainURL || _sockAddr.getPort() != domainPort) || _isInErrorState) { // re-set the domain info so that auth information is reloaded hardReset(); @@ -206,7 +234,8 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) { - if (_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) { + // if it's in the error state, reset and try again. + if ((_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) || _isInErrorState) { // re-set the domain info to connect to new domain hardReset(); @@ -316,6 +345,28 @@ void DomainHandler::connectedToServerless(std::map namedPaths) setIsConnected(true); } +void DomainHandler::loadedErrorDomain(std::map namedPaths) { + auto lookup = namedPaths.find("/"); + QString viewpoint; + if (lookup != namedPaths.end()) { + viewpoint = lookup->second; + } else { + viewpoint = DOMAIN_SPAWNING_POINT; + } + DependencyManager::get()->goToViewpointForPath(viewpoint, QString()); +} + +void DomainHandler::setRedirectErrorState(QUrl errorUrl, QString reasonMessage, int reasonCode, const QString& extraInfo) { + _lastDomainConnectionError = reasonCode; + if (getInterstitialModeEnabled()) { + _errorDomainURL = errorUrl; + _isInErrorState = true; + emit redirectToErrorDomainURL(_errorDomainURL); + } else { + emit domainConnectionRefused(reasonMessage, reasonCode, extraInfo); + } +} + void DomainHandler::requestDomainSettings() { qCDebug(networking) << "Requesting settings from domain server"; @@ -451,7 +502,13 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer(); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index a428110db6..e9ec20ba2e 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -19,6 +19,9 @@ #include #include +#include +#include + #include "HifiSockAddr.h" #include "NetworkPeer.h" #include "NLPacket.h" @@ -50,6 +53,11 @@ public: QString getHostname() const { return _domainURL.host(); } + QUrl getErrorDomainURL(){ return _errorDomainURL; } + void setErrorDomainURL(const QUrl& url); + + int getLastDomainConnectionError() { return _lastDomainConnectionError; } + const QHostAddress& getIP() const { return _sockAddr.getAddress(); } void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); } @@ -78,9 +86,13 @@ public: bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); bool isServerless() const { return _domainURL.scheme() != URL_SCHEME_HIFI; } + bool getInterstitialModeEnabled() const; + void setInterstitialModeEnabled(bool enableInterstitialMode); void connectedToServerless(std::map namedPaths); + void loadedErrorDomain(std::map namedPaths); + QString getViewPointFromNamedPath(QString namedPath); bool hasSettings() const { return !_settingsObject.isEmpty(); } @@ -135,6 +147,11 @@ public: * 4 * The domain already has its maximum number of users. * + * + * TimedOut + * 5 + * Connecting to the domain timed out. + * * * * @typedef {number} Window.ConnectionRefusedReason @@ -144,7 +161,8 @@ public: ProtocolMismatch, LoginError, NotAuthorized, - TooManyUsers + TooManyUsers, + TimedOut }; public slots: @@ -157,6 +175,11 @@ public slots: void processICEResponsePacket(QSharedPointer icePacket); void processDomainServerConnectionDeniedPacket(QSharedPointer message); + // sets domain handler in error state. + void setRedirectErrorState(QUrl errorUrl, QString reasonMessage = "", int reason = -1, const QString& extraInfo = ""); + + bool isInErrorState() { return _isInErrorState; } + private slots: void completedHostnameLookup(const QHostInfo& hostInfo); void completedIceServerHostnameLookup(); @@ -164,6 +187,8 @@ private slots: signals: void domainURLChanged(QUrl domainURL); + void domainConnectionErrorChanged(int reasonCode); + // NOTE: the emission of completedSocketDiscovery does not mean a connection to DS is established // It means that, either from DNS lookup or ICE, we think we have a socket we can talk to DS on void completedSocketDiscovery(); @@ -179,6 +204,7 @@ signals: void settingsReceiveFail(); void domainConnectionRefused(QString reasonMessage, int reason, const QString& extraInfo); + void redirectToErrorDomainURL(QUrl errorDomainURL); void limitOfSilentDomainCheckInsReached(); @@ -190,6 +216,7 @@ private: QUuid _uuid; Node::LocalID _localID; QUrl _domainURL; + QUrl _errorDomainURL; HifiSockAddr _sockAddr; QUuid _assignmentUUID; QUuid _connectionToken; @@ -198,9 +225,12 @@ private: HifiSockAddr _iceServerSockAddr; NetworkPeer _icePeer; bool _isConnected { false }; + bool _isInErrorState { false }; QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; + mutable ReadWriteLockable _interstitialModeSettingLock; + Setting::Handle _enableInterstitialMode{ "enableInterstitialMode", false }; QSet _domainConnectionRefusals; bool _hasCheckedForAccessToken { false }; @@ -210,6 +240,9 @@ private: QTimer _apiRefreshTimer; std::map _namedPaths; + + // domain connection error upon connection refusal. + int _lastDomainConnectionError{ -1 }; }; const QString DOMAIN_SPAWNING_POINT { "/0, -10, 0" }; diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index b6b2369703..db6ed15792 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -56,13 +56,20 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : qRegisterMetaType("ConnectionStep"); auto port = (socketListenPort != INVALID_PORT) ? socketListenPort : LIMITED_NODELIST_LOCAL_PORT.get(); _nodeSocket.bind(QHostAddress::AnyIPv4, port); - qCDebug(networking) << "NodeList socket is listening on" << _nodeSocket.localPort(); + quint16 assignedPort = _nodeSocket.localPort(); + if (socketListenPort != INVALID_PORT && socketListenPort != 0 && socketListenPort != assignedPort) { + qCCritical(networking) << "NodeList is unable to assign requested port of" << socketListenPort; + } + qCDebug(networking) << "NodeList socket is listening on" << assignedPort; if (dtlsListenPort != INVALID_PORT) { // only create the DTLS socket during constructor if a custom port is passed _dtlsSocket = new QUdpSocket(this); _dtlsSocket->bind(QHostAddress::AnyIPv4, dtlsListenPort); + if (dtlsListenPort != 0 && _dtlsSocket->localPort() != dtlsListenPort) { + qCDebug(networking) << "NodeList is unable to assign requested DTLS port of" << dtlsListenPort; + } qCDebug(networking) << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort(); } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index dd351ef940..e458ffab7e 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -273,6 +273,7 @@ void NodeList::reset(bool skipDomainHandlerReset) { // refresh the owner UUID to the NULL UUID setSessionUUID(QUuid()); + setSessionLocalID(Node::NULL_LOCAL_ID); // if we setup the DTLS socket, also disconnect from the DTLS socket readyRead() so it can handle handshaking if (_dtlsSocket) { @@ -647,6 +648,23 @@ void NodeList::processDomainServerList(QSharedPointer message) Node::LocalID newLocalID; packetStream >> newUUID; packetStream >> newLocalID; + + // when connected, if the session ID or local ID were not null and changed, we should reset + auto currentLocalID = getSessionLocalID(); + auto currentSessionID = getSessionUUID(); + if (_domainHandler.isConnected() && + ((currentLocalID != Node::NULL_LOCAL_ID && newLocalID != currentLocalID) || + (!currentSessionID.isNull() && newUUID != currentSessionID))) { + qCDebug(networking) << "Local ID or Session ID changed while connected to domain - forcing NodeList reset"; + + // reset the nodelist, but don't do a domain handler reset since we're about to process a good domain list + reset(true); + + // tell the domain handler that we're no longer connected so that below + // it can re-perform actions as if we just connected + _domainHandler.setIsConnected(false); + } + setSessionLocalID(newLocalID); setSessionUUID(newUUID); diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index a5ee417939..c05daf0217 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -38,10 +38,10 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall if (_disabled.get()) { return; } - + auto accountManager = DependencyManager::get(); QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - + // Adding the action name QHttpPart actionPart; actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\""); @@ -53,7 +53,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall elapsedPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"elapsed_ms\""); elapsedPart.setBody(QString::number(_timer.elapsed()).toLocal8Bit()); multipart->append(elapsedPart); - + // If there are action details, add them to the multipart if (!details.isEmpty()) { QHttpPart detailsPart; @@ -62,13 +62,13 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall detailsPart.setBody(QJsonDocument(details).toJson(QJsonDocument::Compact)); multipart->append(detailsPart); } - + // if no callbacks specified, call our owns if (params.isEmpty()) { params.callbackReceiver = this; params.errorCallbackMethod = "requestError"; } - + accountManager->sendRequest(USER_ACTIVITY_URL, AccountManagerAuth::Optional, QNetworkAccessManager::PostOperation, @@ -88,7 +88,7 @@ void UserActivityLogger::launch(QString applicationVersion, bool previousSession actionDetails.insert(VERSION_KEY, applicationVersion); actionDetails.insert(CRASH_KEY, previousSessionCrashed); actionDetails.insert(RUNTIME_KEY, previousSessionRuntime); - + logAction(ACTION_NAME, actionDetails); } @@ -105,9 +105,9 @@ void UserActivityLogger::changedDisplayName(QString displayName) { const QString ACTION_NAME = "changed_display_name"; QJsonObject actionDetails; const QString DISPLAY_NAME = "display_name"; - + actionDetails.insert(DISPLAY_NAME, displayName); - + logAction(ACTION_NAME, actionDetails); } @@ -116,10 +116,10 @@ void UserActivityLogger::changedModel(QString typeOfModel, QString modelURL) { QJsonObject actionDetails; const QString TYPE_OF_MODEL = "type_of_model"; const QString MODEL_URL = "model_url"; - + actionDetails.insert(TYPE_OF_MODEL, typeOfModel); actionDetails.insert(MODEL_URL, modelURL); - + logAction(ACTION_NAME, actionDetails); } @@ -127,9 +127,9 @@ void UserActivityLogger::changedDomain(QString domainURL) { const QString ACTION_NAME = "changed_domain"; QJsonObject actionDetails; const QString DOMAIN_URL = "domain_url"; - + actionDetails.insert(DOMAIN_URL, domainURL); - + logAction(ACTION_NAME, actionDetails); } @@ -151,10 +151,10 @@ void UserActivityLogger::connectedDevice(QString typeOfDevice, QString deviceNam QJsonObject actionDetails; const QString TYPE_OF_DEVICE = "type_of_device"; const QString DEVICE_NAME = "device_name"; - + actionDetails.insert(TYPE_OF_DEVICE, typeOfDevice); actionDetails.insert(DEVICE_NAME, deviceName); - + logAction(ACTION_NAME, actionDetails); } @@ -163,9 +163,9 @@ void UserActivityLogger::loadedScript(QString scriptName) { const QString ACTION_NAME = "loaded_script"; QJsonObject actionDetails; const QString SCRIPT_NAME = "script_name"; - + actionDetails.insert(SCRIPT_NAME, scriptName); - + logAction(ACTION_NAME, actionDetails); } @@ -199,10 +199,10 @@ void UserActivityLogger::wentTo(AddressManager::LookupTrigger lookupTrigger, QSt const QString TRIGGER_TYPE_KEY = "trigger"; const QString DESTINATION_TYPE_KEY = "destination_type"; const QString DESTINATION_NAME_KEY = "detination_name"; - + actionDetails.insert(TRIGGER_TYPE_KEY, trigger); actionDetails.insert(DESTINATION_TYPE_KEY, destinationType); actionDetails.insert(DESTINATION_NAME_KEY, destinationName); - + logAction(ACTION_NAME, actionDetails); } diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index c378987cd0..44220df8f8 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -37,6 +37,7 @@ Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) : _shouldChangeSocketOptions(shouldChangeSocketOptions) { connect(&_udpSocket, &QUdpSocket::readyRead, this, &Socket::readPendingDatagrams); + connect(this, &Socket::pendingDatagrams, this, &Socket::processPendingDatagrams, Qt::QueuedConnection); // make sure we hear about errors and state changes from the underlying socket connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), @@ -315,55 +316,85 @@ void Socket::checkForReadyReadBackup() { } void Socket::readPendingDatagrams() { + int packetsRead = 0; + int packetSizeWithHeader = -1; - - while (_udpSocket.hasPendingDatagrams() && (packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) { - - // we're reading a packet so re-start the readyRead backup timer - _readyReadBackupTimer->start(); - + // Max datagrams to read before processing: + static const int MAX_DATAGRAMS_CONSECUTIVELY = 10000; + while (_udpSocket.hasPendingDatagrams() + && (packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1 + && packetsRead <= MAX_DATAGRAMS_CONSECUTIVELY) { // grab a time point we can mark as the receive time of this packet auto receiveTime = p_high_resolution_clock::now(); - // setup a HifiSockAddr to read into - HifiSockAddr senderSockAddr; // setup a buffer to read the packet into auto buffer = std::unique_ptr(new char[packetSizeWithHeader]); + QHostAddress senderAddress; + quint16 senderPort; + // pull the datagram auto sizeRead = _udpSocket.readDatagram(buffer.get(), packetSizeWithHeader, - senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); + &senderAddress, &senderPort); - // save information for this packet, in case it is the one that sticks readyRead - _lastPacketSizeRead = sizeRead; - _lastPacketSockAddr = senderSockAddr; - - if (sizeRead <= 0) { - // we either didn't pull anything for this packet or there was an error reading (this seems to trigger - // on windows even if there's not a packet available) + // we either didn't pull anything for this packet or there was an error reading (this seems to trigger + // on windows even if there's not a packet available) + if (sizeRead < 0) { continue; } - auto it = _unfilteredHandlers.find(senderSockAddr); + _incomingDatagrams.push_back({ senderAddress, senderPort, packetSizeWithHeader, + std::move(buffer), receiveTime }); + ++packetsRead; + } + + if (packetsRead > _maxDatagramsRead) { + _maxDatagramsRead = packetsRead; + qCDebug(networking) << "readPendingDatagrams: Datagrams read:" << packetsRead; + } + emit pendingDatagrams(packetsRead); +} + +void Socket::processPendingDatagrams(int) { + // setup a HifiSockAddr to read into + HifiSockAddr senderSockAddr; + + while (!_incomingDatagrams.empty()) { + auto& datagram = _incomingDatagrams.front(); + senderSockAddr.setAddress(datagram._senderAddress); + senderSockAddr.setPort(datagram._senderPort); + int datagramSize = datagram._datagramLength; + auto receiveTime = datagram._receiveTime; + + // we're reading a packet so re-start the readyRead backup timer + _readyReadBackupTimer->start(); + + // save information for this packet, in case it is the one that sticks readyRead + _lastPacketSizeRead = datagramSize; + _lastPacketSockAddr = senderSockAddr; + + // Process unfiltered packets first. + auto it = _unfilteredHandlers.find(senderSockAddr); if (it != _unfilteredHandlers.end()) { - // we have a registered unfiltered handler for this HifiSockAddr - call that and return + // we have a registered unfiltered handler for this HifiSockAddr (eg. STUN packet) - call that and return if (it->second) { - auto basePacket = BasePacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + auto basePacket = BasePacket::fromReceivedPacket(std::move(datagram._datagram), + datagramSize, senderSockAddr); basePacket->setReceiveTime(receiveTime); it->second(std::move(basePacket)); } - + _incomingDatagrams.pop_front(); continue; } // check if this was a control packet or a data packet - bool isControlPacket = *reinterpret_cast(buffer.get()) & CONTROL_BIT_MASK; + bool isControlPacket = *reinterpret_cast(datagram._datagram.get()) & CONTROL_BIT_MASK; if (isControlPacket) { // setup a control packet from the data we just read - auto controlPacket = ControlPacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + auto controlPacket = ControlPacket::fromReceivedPacket(std::move(datagram._datagram), datagramSize, senderSockAddr); controlPacket->setReceiveTime(receiveTime); // move this control packet to the matching connection, if there is one @@ -375,13 +406,13 @@ void Socket::readPendingDatagrams() { } else { // setup a Packet from the data we just read - auto packet = Packet::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + auto packet = Packet::fromReceivedPacket(std::move(datagram._datagram), datagramSize, senderSockAddr); packet->setReceiveTime(receiveTime); // save the sequence number in case this is the packet that sticks readyRead _lastReceivedSequenceNumber = packet->getSequenceNumber(); - // call our verification operator to see if this packet is verified + // call our hash verification operator to see if this packet is verified if (!_packetFilterOperator || _packetFilterOperator(*packet)) { if (packet->isReliable()) { // if this was a reliable packet then signal the matching connection with the sequence number @@ -395,6 +426,7 @@ void Socket::readPendingDatagrams() { qCDebug(networking) << "Can't process packet: version" << (unsigned int)NLPacket::versionInHeader(*packet) << ", type" << NLPacket::typeInHeader(*packet); #endif + _incomingDatagrams.pop_front(); continue; } } @@ -410,6 +442,8 @@ void Socket::readPendingDatagrams() { } } } + + _incomingDatagrams.pop_front(); } } diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 1f28592c83..ef4a457116 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -94,6 +95,7 @@ public: signals: void clientHandshakeRequestComplete(const HifiSockAddr& sockAddr); + void pendingDatagrams(int datagramCount); public slots: void cleanupConnection(HifiSockAddr sockAddr); @@ -101,6 +103,7 @@ public slots: private slots: void readPendingDatagrams(); + void processPendingDatagrams(int datagramCount); void checkForReadyReadBackup(); void handleSocketError(QAbstractSocket::SocketError socketError); @@ -144,6 +147,17 @@ private: int _lastPacketSizeRead { 0 }; SequenceNumber _lastReceivedSequenceNumber; HifiSockAddr _lastPacketSockAddr; + + struct Datagram { + QHostAddress _senderAddress; + int _senderPort; + int _datagramLength; + std::unique_ptr _datagram; + p_high_resolution_clock::time_point _receiveTime; + }; + + std::list _incomingDatagrams; + int _maxDatagramsRead { 0 }; friend UDTTest; }; diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 9bb0e25982..3d387e0956 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -68,55 +68,12 @@ Octree::~Octree() { eraseAllOctreeElements(false); } - -// Inserts the value and key into three arrays sorted by the key array, the first array is the value, -// the second array is a sorted key for the value, the third array is the index for the value in it original -// non-sorted array -// returns -1 if size exceeded -// originalIndexArray is optional -int insertOctreeElementIntoSortedArrays(const OctreeElementPointer& value, float key, int originalIndex, - OctreeElementPointer* valueArray, float* keyArray, int* originalIndexArray, - int currentCount, int maxCount) { - - if (currentCount < maxCount) { - int i = 0; - if (currentCount > 0) { - while (i < currentCount && key > keyArray[i]) { - i++; - } - // i is our desired location - // shift array elements to the right - if (i < currentCount && i+1 < maxCount) { - for (int j = currentCount - 1; j > i; j--) { - valueArray[j] = valueArray[j - 1]; - keyArray[j] = keyArray[j - 1]; - } - } - } - // place new element at i - valueArray[i] = value; - keyArray[i] = key; - if (originalIndexArray) { - originalIndexArray[i] = originalIndex; - } - return currentCount + 1; - } - return -1; // error case -} - - - // Recurses voxel tree calling the RecurseOctreeOperation function for each element. // stops recursion if operation function returns false. void Octree::recurseTreeWithOperation(const RecurseOctreeOperation& operation, void* extraData) { recurseElementWithOperation(_rootElement, operation, extraData); } -// Recurses voxel tree calling the RecurseOctreePostFixOperation function for each element in post-fix order. -void Octree::recurseTreeWithPostOperation(const RecurseOctreeOperation& operation, void* extraData) { - recurseElementWithPostOperation(_rootElement, operation, extraData); -} - // Recurses voxel element with an operation function void Octree::recurseElementWithOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, void* extraData, int recursionCount) { @@ -129,71 +86,53 @@ void Octree::recurseElementWithOperation(const OctreeElementPointer& element, co for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { OctreeElementPointer child = element->getChildAtIndex(i); if (child) { - recurseElementWithOperation(child, operation, extraData, recursionCount+1); + recurseElementWithOperation(child, operation, extraData, recursionCount + 1); } } } } -// Recurses voxel element with an operation function -void Octree::recurseElementWithPostOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, - void* extraData, int recursionCount) { +void Octree::recurseTreeWithOperationSorted(const RecurseOctreeOperation& operation, const RecurseOctreeSortingOperation& sortingOperation, void* extraData) { + recurseElementWithOperationSorted(_rootElement, operation, sortingOperation, extraData); +} + +// Recurses voxel element with an operation function, calling operation on its children in a specific order +bool Octree::recurseElementWithOperationSorted(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, + const RecurseOctreeSortingOperation& sortingOperation, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - HIFI_FCDEBUG(octree(), "Octree::recurseElementWithPostOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); - return; + HIFI_FCDEBUG(octree(), "Octree::recurseElementWithOperationSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); + // If we go too deep, we want to keep searching other paths + return true; } + bool keepSearching = operation(element, extraData); + + std::vector sortedChildren; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { OctreeElementPointer child = element->getChildAtIndex(i); if (child) { - recurseElementWithPostOperation(child, operation, extraData, recursionCount+1); - } - } - operation(element, extraData); -} - -// Recurses voxel tree calling the RecurseOctreeOperation function for each element. -// stops recursion if operation function returns false. -void Octree::recurseTreeWithOperationDistanceSorted(const RecurseOctreeOperation& operation, - const glm::vec3& point, void* extraData) { - - recurseElementWithOperationDistanceSorted(_rootElement, operation, point, extraData); -} - -// Recurses voxel element with an operation function -void Octree::recurseElementWithOperationDistanceSorted(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, - const glm::vec3& point, void* extraData, int recursionCount) { - - if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - HIFI_FCDEBUG(octree(), "Octree::recurseElementWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); - return; - } - - if (operation(element, extraData)) { - // determine the distance sorted order of our children - OctreeElementPointer sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; - float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - int indexOfChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - int currentCount = 0; - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - OctreeElementPointer childElement = element->getChildAtIndex(i); - if (childElement) { - // chance to optimize, doesn't need to be actual distance!! Could be distance squared - float distanceSquared = childElement->distanceSquareToPoint(point); - currentCount = insertOctreeElementIntoSortedArrays(childElement, distanceSquared, i, - sortedChildren, (float*)&distancesToChildren, - (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); - } - } - - for (int i = 0; i < currentCount; i++) { - OctreeElementPointer childElement = sortedChildren[i]; - if (childElement) { - recurseElementWithOperationDistanceSorted(childElement, operation, point, extraData); + float priority = sortingOperation(child, extraData); + if (priority < FLT_MAX) { + sortedChildren.emplace_back(priority, child); } } } + + if (sortedChildren.size() > 1) { + static auto comparator = [](const SortedChild& left, const SortedChild& right) { return left.first < right.first; }; + std::sort(sortedChildren.begin(), sortedChildren.end(), comparator); + } + + for (auto it = sortedChildren.begin(); it != sortedChildren.end(); ++it) { + const SortedChild& sortedChild = *it; + // Our children were sorted, so if one hits something, we don't need to check the others + if (!recurseElementWithOperationSorted(sortedChild.second, operation, sortingOperation, extraData, recursionCount + 1)) { + return false; + } + } + // We checked all our children and didn't find anything. + // Stop if we hit something in this element. Continue if we didn't. + return keepSearching; } void Octree::recurseTreeWithOperator(RecurseOctreeOperator* operatorObject) { diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index b827ed7cd0..a2b2f227cb 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -49,6 +49,9 @@ public: // Callback function, for recuseTreeWithOperation using RecurseOctreeOperation = std::function; +// Function for sorting octree children during recursion. If return value == FLT_MAX, child is discarded +using RecurseOctreeSortingOperation = std::function; +using SortedChild = std::pair; typedef QHash CubeList; const bool NO_EXISTS_BITS = false; @@ -163,17 +166,10 @@ public: OctreeElementPointer getOrCreateChildElementContaining(const AACube& box); void recurseTreeWithOperation(const RecurseOctreeOperation& operation, void* extraData = NULL); - void recurseTreeWithPostOperation(const RecurseOctreeOperation& operation, void* extraData = NULL); - - /// \param operation type of operation - /// \param point point in world-frame (meters) - /// \param extraData hook for user data to be interpreted by special context - void recurseTreeWithOperationDistanceSorted(const RecurseOctreeOperation& operation, - const glm::vec3& point, void* extraData = NULL); + void recurseTreeWithOperationSorted(const RecurseOctreeOperation& operation, const RecurseOctreeSortingOperation& sortingOperation, void* extraData = NULL); void recurseTreeWithOperator(RecurseOctreeOperator* operatorObject); - bool isDirty() const { return _isDirty; } void clearDirtyBit() { _isDirty = false; } void setDirtyBit() { _isDirty = true; } @@ -227,14 +223,8 @@ public: void recurseElementWithOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, void* extraData, int recursionCount = 0); - - /// Traverse child nodes of node applying operation in post-fix order - /// - void recurseElementWithPostOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, - void* extraData, int recursionCount = 0); - - void recurseElementWithOperationDistanceSorted(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, - const glm::vec3& point, void* extraData, int recursionCount = 0); + bool recurseElementWithOperationSorted(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, + const RecurseOctreeSortingOperation& sortingOperation, void* extraData, int recursionCount = 0); bool recurseElementWithOperator(const OctreeElementPointer& element, RecurseOctreeOperator* operatorObject, int recursionCount = 0); diff --git a/libraries/octree/src/OctreeUtils.cpp b/libraries/octree/src/OctreeUtils.cpp index 8eaf22e198..7ed9c2ed3c 100644 --- a/libraries/octree/src/OctreeUtils.cpp +++ b/libraries/octree/src/OctreeUtils.cpp @@ -64,10 +64,14 @@ float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeSc return voxelSizeScale / powf(2.0f, renderLevel); } -float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) { +float getPerspectiveAccuracyAngleTan(float octreeSizeScale, int boundaryLevelAdjust) { const float maxScale = (float)TREE_SCALE; float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / OCTREE_TO_MESH_RATIO; - return atan(maxScale / visibleDistanceAtMaxScale); + return (maxScale / visibleDistanceAtMaxScale); +} + +float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) { + return atan(getPerspectiveAccuracyAngleTan(octreeSizeScale, boundaryLevelAdjust)); } float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust) { @@ -75,9 +79,3 @@ float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust const float smallestSize = 0.01f; return (smallestSize * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT) / boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale); } - -bool isAngularSizeBigEnough(glm::vec3 position, const AACube& cube, float lodScaleFactor, float minDiameter) { - float distance = glm::distance(cube.calcCenter(), position) + MIN_VISIBLE_DISTANCE; - float angularDiameter = cube.getScale() / distance; - return angularDiameter > minDiameter * lodScaleFactor; -} diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index dff56cad64..eedbfe8bda 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -29,6 +29,7 @@ float calculateRenderAccuracy(const glm::vec3& position, float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale); +float getPerspectiveAccuracyAngleTan(float octreeSizeScale, int boundaryLevelAdjust); float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust); float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust); @@ -38,6 +39,4 @@ const float MIN_ELEMENT_ANGULAR_DIAMETER = 0.0043301f; // radians const float MIN_ENTITY_ANGULAR_DIAMETER = MIN_ELEMENT_ANGULAR_DIAMETER * SQRT_THREE; const float MIN_VISIBLE_DISTANCE = 0.0001f; // helps avoid divide-by-zero check -bool isAngularSizeBigEnough(glm::vec3 position, const AACube& cube, float lodScaleFactor, float minDiameter); - #endif // hifi_OctreeUtils_h diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 925cfee740..5478221607 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -446,8 +446,9 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { void EntityMotionState::updateSendVelocities() { if (!_body->isActive()) { - // make sure all derivatives are zero - clearObjectVelocities(); + if (!_body->isKinematicObject()) { + clearObjectVelocities(); + } // we pretend we sent the inactive update for this object _numInactiveUpdates = 1; } else { @@ -778,8 +779,10 @@ void EntityMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& ma bool EntityMotionState::shouldSendBid() const { // NOTE: this method is only ever called when the entity's simulation is NOT locally owned - return _body->isActive() && (_region == workload::Region::R1) && - glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) >= _entity->getSimulationPriority(); + return _body->isActive() + && (_region == workload::Region::R1) + && glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) >= _entity->getSimulationPriority() + && !_entity->getLocked(); } uint8_t EntityMotionState::computeFinalBidPriority() const { diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 5666e75aa1..2004f8953d 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -138,7 +138,6 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { btRigidBody* body = motionState->getRigidBody(); if (body) { body->forceActivationState(ISLAND_SLEEPING); - motionState->updateSendVelocities(); // has side-effect of zeroing entity velocities for inactive body } // send packet to remove ownership diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 210c66fbea..8343919ae1 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -286,6 +286,7 @@ void PhysicsEngine::reinsertObject(ObjectMotionState* object) { void PhysicsEngine::processTransaction(PhysicsEngine::Transaction& transaction) { // removes for (auto object : transaction.objectsToRemove) { + bumpAndPruneContacts(object); btRigidBody* body = object->getRigidBody(); if (body) { removeDynamicsForBody(body); diff --git a/libraries/pointers/src/Pick.cpp b/libraries/pointers/src/Pick.cpp index 4315409bdb..7ac53a2643 100644 --- a/libraries/pointers/src/Pick.cpp +++ b/libraries/pointers/src/Pick.cpp @@ -72,11 +72,15 @@ PickResultPointer PickQuery::getPrevPickResult() const { void PickQuery::setIgnoreItems(const QVector& ignoreItems) { withWriteLock([&] { _ignoreItems = ignoreItems; + // We sort these items here so the PickCacheOptimizer can catch cases where two picks have the same ignoreItems in a different order + std::sort(_ignoreItems.begin(), _ignoreItems.end(), std::less()); }); } void PickQuery::setIncludeItems(const QVector& includeItems) { withWriteLock([&] { _includeItems = includeItems; + // We sort these items here so the PickCacheOptimizer can catch cases where two picks have the same includeItems in a different order + std::sort(_includeItems.begin(), _includeItems.end(), std::less()); }); } \ No newline at end of file diff --git a/libraries/pointers/src/PickCacheOptimizer.h b/libraries/pointers/src/PickCacheOptimizer.h index 448d67b5a9..c74e84937b 100644 --- a/libraries/pointers/src/PickCacheOptimizer.h +++ b/libraries/pointers/src/PickCacheOptimizer.h @@ -37,7 +37,7 @@ template class PickCacheOptimizer { public: - void update(std::unordered_map>& picks, uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD); + QVector4D update(std::unordered_map>& picks, uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD); protected: typedef std::unordered_map> PickCache; @@ -67,8 +67,9 @@ void PickCacheOptimizer::cacheResult(const bool needToCompareResults, const P } template -void PickCacheOptimizer::update(std::unordered_map>& picks, +QVector4D PickCacheOptimizer::update(std::unordered_map>& picks, uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD) { + QVector4D numIntersectionsComputed; PickCache results; const uint32_t INVALID_PICK_ID = 0; auto itr = picks.begin(); @@ -91,6 +92,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) { PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick); + numIntersectionsComputed[0]++; if (entityRes) { cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick); } @@ -101,6 +103,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) { PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick); + numIntersectionsComputed[1]++; if (overlayRes) { cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick); } @@ -111,6 +114,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) { PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick); + numIntersectionsComputed[2]++; if (avatarRes) { cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick); } @@ -122,6 +126,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetFilter().getHUDFlags(), QVector(), QVector() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) { PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick); + numIntersectionsComputed[3]++; if (hudRes) { cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick); } @@ -145,6 +150,7 @@ void PickCacheOptimizer::update(std::unordered_mapsecond].erase(uid); _typeMap.erase(uid); + _totalPickCounts[type->second]--; } }); } @@ -104,12 +106,12 @@ void PickManager::update() { }); bool shouldPickHUD = _shouldPickHUDOperator(); - // we pass the same expiry to both updates, but the stylus updates are relatively cheap - // and the rayPicks updae will ALWAYS update at least one ray even when there is no budget - _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false); - _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD); - _parabolaPickCacheOptimizer.update(cachedPicks[PickQuery::Parabola], _nextPickToUpdate[PickQuery::Parabola], expiry, shouldPickHUD); - _collisionPickCacheOptimizer.update(cachedPicks[PickQuery::Collision], _nextPickToUpdate[PickQuery::Collision], expiry, false); + // FIXME: give each type its own expiry + // Each type will update at least one pick, regardless of the expiry + _updatedPickCounts[PickQuery::Stylus] = _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false); + _updatedPickCounts[PickQuery::Ray] = _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD); + _updatedPickCounts[PickQuery::Parabola] = _parabolaPickCacheOptimizer.update(cachedPicks[PickQuery::Parabola], _nextPickToUpdate[PickQuery::Parabola], expiry, shouldPickHUD); + _updatedPickCounts[PickQuery::Collision] = _collisionPickCacheOptimizer.update(cachedPicks[PickQuery::Collision], _nextPickToUpdate[PickQuery::Collision], expiry, false); } bool PickManager::isLeftHand(unsigned int uid) { diff --git a/libraries/pointers/src/PickManager.h b/libraries/pointers/src/PickManager.h index 3209a4c037..cf911236a1 100644 --- a/libraries/pointers/src/PickManager.h +++ b/libraries/pointers/src/PickManager.h @@ -16,7 +16,10 @@ #include -class PickManager : public Dependency, protected ReadWriteLockable { +#include + +class PickManager : public QObject, public Dependency, protected ReadWriteLockable { + Q_OBJECT SINGLETON_DEPENDENCY public: @@ -55,7 +58,19 @@ public: unsigned int getPerFrameTimeBudget() const { return _perFrameTimeBudget; } void setPerFrameTimeBudget(unsigned int numUsecs) { _perFrameTimeBudget = numUsecs; } + bool getForceCoarsePicking() { return _forceCoarsePicking; } + + const std::vector& getUpdatedPickCounts() { return _updatedPickCounts; } + const std::vector& getTotalPickCounts() { return _totalPickCounts; } + +public slots: + void setForceCoarsePicking(bool forceCoarsePicking) { _forceCoarsePicking = forceCoarsePicking; } + protected: + std::vector _updatedPickCounts { PickQuery::NUM_PICK_TYPES }; + std::vector _totalPickCounts { 0, 0, 0, 0 }; + + bool _forceCoarsePicking { false }; std::function _shouldPickHUDOperator; std::function _calculatePos2DFromHUDOperator; diff --git a/libraries/procedural/src/procedural/ShaderConstants.h b/libraries/procedural/src/procedural/ShaderConstants.h index 1995996c97..bfbf2a2691 100644 --- a/libraries/procedural/src/procedural/ShaderConstants.h +++ b/libraries/procedural/src/procedural/ShaderConstants.h @@ -14,7 +14,6 @@ #ifndef PROCEDURAL_SHADER_CONSTANTS_H #define PROCEDURAL_SHADER_CONSTANTS_H -// Polyvox #define PROCEDURAL_UNIFORM_TIME 200 #define PROCEDURAL_UNIFORM_DATE 201 #define PROCEDURAL_UNIFORM_FRAME_COUNT 202 diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 2b17ba3c01..17c13df19a 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -29,7 +29,6 @@ namespace ru { - using render_utils::slot::uniform::Uniform; using render_utils::slot::texture::Texture; using render_utils::slot::buffer::Buffer; } @@ -39,13 +38,7 @@ namespace gr { using graphics::slot::buffer::Buffer; } -#define ANTIALIASING_USE_TAA 1 - #if !ANTIALIASING_USE_TAA -#include "fxaa_vert.h" -#include "fxaa_frag.h" -#include "fxaa_blend_frag.h" - Antialiasing::Antialiasing() { _geometryId = DependencyManager::get()->allocateID(); @@ -58,30 +51,9 @@ Antialiasing::~Antialiasing() { } } -const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(RenderArgs* args) { - int width = args->_viewport.z; - int height = args->_viewport.w; - - if (_antialiasingBuffer && _antialiasingBuffer->getSize() != uvec2(width, height)) { - _antialiasingBuffer.reset(); - } - - if (!_antialiasingBuffer) { - // Link the antialiasing FBO to texture - _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); - auto format = gpu::Element::COLOR_SRGBA_32; - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - _antialiasingTexture = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); - _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); - } - +const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { if (!_antialiasingPipeline) { - auto vs = fxaa_vert::getShader(); - auto ps = fxaa_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - _texcoordOffsetLoc = program->getUniforms().findLocation("texcoordOffset"); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::fxaa); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(false, false, gpu::LESS_EQUAL); @@ -96,9 +68,7 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(RenderArgs* ar const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { if (!_blendPipeline) { - auto vs = fxaa_vert::getShader(); - auto ps = fxaa_blend_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::fxaa_blend); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(false, false, gpu::LESS_EQUAL); PrepareStencil::testNoAA(*state); @@ -119,13 +89,30 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.enableStereo(false); batch.setViewportTransform(args->_viewport); - // FIXME: NEED to simplify that code to avoid all the GeometryCahce call, this is purely pixel manipulation - float fbWidth = renderContext->args->_viewport.z; - float fbHeight = renderContext->args->_viewport.w; - // float sMin = args->_viewport.x / fbWidth; - // float sWidth = args->_viewport.z / fbWidth; - // float tMin = args->_viewport.y / fbHeight; - // float tHeight = args->_viewport.w / fbHeight; + if (!_paramsBuffer) { + _paramsBuffer = std::make_shared(sizeof(glm::vec4), nullptr); + } + + { + int width = args->_viewport.z; + int height = args->_viewport.w; + if (_antialiasingBuffer && _antialiasingBuffer->getSize() != uvec2(width, height)) { + _antialiasingBuffer.reset(); + } + + if (!_antialiasingBuffer) { + // Link the antialiasing FBO to texture + _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); + auto format = gpu::Element::COLOR_SRGBA_32; + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); + _antialiasingTexture = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); + _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); + glm::vec2 fbExtent { args->_viewport.z, args->_viewport.w }; + glm::vec2 inverseFbExtent = 1.0f / fbExtent; + _paramsBuffer->setSubData(0, glm::vec4(inverseFbExtent, 0.0, 0.0)); + } + } + glm::mat4 projMat; Transform viewMat; @@ -136,40 +123,18 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.setModelTransform(Transform()); // FXAA step - auto pipeline = getAntialiasingPipeline(renderContext->args); + auto pipeline = getAntialiasingPipeline(); batch.setResourceTexture(0, sourceBuffer->getRenderBuffer(0)); batch.setFramebuffer(_antialiasingBuffer); batch.setPipeline(pipeline); - - // initialize the view-space unpacking uniforms using frustum data - float left, right, bottom, top, nearVal, farVal; - glm::vec4 nearClipPlane, farClipPlane; - - args->getViewFrustum().computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); - - // float depthScale = (farVal - nearVal) / farVal; - // float nearScale = -1.0f / nearVal; - // float depthTexCoordScaleS = (right - left) * nearScale / sWidth; - // float depthTexCoordScaleT = (top - bottom) * nearScale / tHeight; - // float depthTexCoordOffsetS = left * nearScale - sMin * depthTexCoordScaleS; - // float depthTexCoordOffsetT = bottom * nearScale - tMin * depthTexCoordScaleT; - - batch._glUniform2f(_texcoordOffsetLoc, 1.0f / fbWidth, 1.0f / fbHeight); - - glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f); - glm::vec2 bottomLeft(-1.0f, -1.0f); - glm::vec2 topRight(1.0f, 1.0f); - glm::vec2 texCoordTopLeft(0.0f, 0.0f); - glm::vec2 texCoordBottomRight(1.0f, 1.0f); - DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color, _geometryId); - + batch.setUniformBuffer(0, _paramsBuffer); + batch.draw(gpu::TRIANGLE_STRIP, 4); // Blend step batch.setResourceTexture(0, _antialiasingTexture); batch.setFramebuffer(sourceBuffer); batch.setPipeline(getBlendPipeline()); - - DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color, _geometryId); + batch.draw(gpu::TRIANGLE_STRIP, 4); }); } #else @@ -314,7 +279,11 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const // Must match the bindg point in the fxaa_blend.slf shader batch.setResourceFramebufferSwapChainTexture(0, _antialiasingBuffers, 1); // Disable sharpen if FXAA - batch._glUniform1f(ru::Uniform::TaaSharpenIntensity, _sharpen * _params.get().regionInfo.z); + if (!_blendParamsBuffer) { + _blendParamsBuffer = std::make_shared(sizeof(glm::vec4), nullptr); + } + _blendParamsBuffer->setSubData(0, _sharpen * _params.get().regionInfo.z); + batch.setUniformBuffer(0, _blendParamsBuffer); } batch.draw(gpu::TRIANGLE_STRIP, 4); batch.advance(_antialiasingBuffers); diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index 61da352154..936ade043d 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -134,6 +134,10 @@ signals: #define SET_BIT(bitfield, bitIndex, value) bitfield = ((bitfield) & ~(1 << (bitIndex))) | ((value) << (bitIndex)) #define GET_BIT(bitfield, bitIndex) ((bitfield) & (1 << (bitIndex))) +#define ANTIALIASING_USE_TAA 1 + +#if ANTIALIASING_USE_TAA + struct TAAParams { float nope{ 0.0f }; float blend{ 0.15f }; @@ -186,7 +190,7 @@ private: gpu::FramebufferSwapChainPointer _antialiasingBuffers; gpu::TexturePointer _antialiasingTextures[2]; - + gpu::BufferPointer _blendParamsBuffer; gpu::PipelinePointer _antialiasingPipeline; gpu::PipelinePointer _blendPipeline; gpu::PipelinePointer _debugBlendPipeline; @@ -197,7 +201,7 @@ private: }; -/* +#else class AntiAliasingConfig : public render::Job::Config { Q_OBJECT Q_PROPERTY(bool enabled MEMBER enabled) @@ -219,18 +223,15 @@ public: const gpu::PipelinePointer& getBlendPipeline(); private: - - // Uniforms for AA - gpu::int32 _texcoordOffsetLoc; - gpu::FramebufferPointer _antialiasingBuffer; gpu::TexturePointer _antialiasingTexture; + gpu::BufferPointer _paramsBuffer; gpu::PipelinePointer _antialiasingPipeline; gpu::PipelinePointer _blendPipeline; int _geometryId { 0 }; }; -*/ +#endif #endif // hifi_AntialiasingEffect_h diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index 3a35e736a9..e6217eb825 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -184,6 +184,7 @@ void BloomDraw::run(const render::RenderContextPointer& renderContext, const Inp } DebugBloom::DebugBloom() { + _params = std::make_shared(sizeof(glm::vec4), nullptr); } void DebugBloom::configure(const Config& config) { @@ -227,7 +228,8 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In Transform modelTransform; if (_mode == DebugBloomConfig::MODE_ALL_LEVELS) { - batch._glUniform4f(gpu::slot::uniform::TexCoordRect, 0.0f, 0.0f, 1.f, 1.f); + _params->setSubData(0, vec4(0.0f, 0.0f, 1.f, 1.f)); + batch.setUniformBuffer(0, _params); modelTransform = gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, args->_viewport / 2); modelTransform.postTranslate(glm::vec3(-1.0f, 1.0f, 0.0f)); @@ -255,7 +257,8 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In viewport.z /= 2; - batch._glUniform4f(gpu::slot::uniform::TexCoordRect, 0.5f, 0.0f, 0.5f, 1.f); + _params->setSubData(0, vec4(0.5f, 0.0f, 0.5f, 1.f)); + batch.setUniformBuffer(0, _params); modelTransform = gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, viewport); modelTransform.postTranslate(glm::vec3(-1.0f, 0.0f, 0.0f)); diff --git a/libraries/render-utils/src/BloomEffect.h b/libraries/render-utils/src/BloomEffect.h index 7789c70190..12c94bb929 100644 --- a/libraries/render-utils/src/BloomEffect.h +++ b/libraries/render-utils/src/BloomEffect.h @@ -121,6 +121,7 @@ public: private: gpu::PipelinePointer _pipeline; + gpu::BufferPointer _params; DebugBloomConfig::Mode _mode; }; diff --git a/libraries/render-utils/src/BloomThreshold.shared.slh b/libraries/render-utils/src/BloomThreshold.shared.slh index 8aaf8ec311..5ad490a1ca 100644 --- a/libraries/render-utils/src/BloomThreshold.shared.slh +++ b/libraries/render-utils/src/BloomThreshold.shared.slh @@ -8,7 +8,7 @@ struct Parameters { BT_VEC2 _deltaUV; - float _threshold; + float _threshold; int _sampleCount; }; diff --git a/libraries/render-utils/src/BloomThreshold.slf b/libraries/render-utils/src/BloomThreshold.slf index 621aa31622..47a1fb0d9f 100644 --- a/libraries/render-utils/src/BloomThreshold.slf +++ b/libraries/render-utils/src/BloomThreshold.slf @@ -30,7 +30,7 @@ void main(void) { for (int x=0 ; xgetNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { + if (!fbxGeometry.meshes[i].blendshapes.empty() && _blendedVertexBuffers.find(i) == _blendedVertexBuffers.end()) { + initializeBlendshapes(fbxGeometry.meshes[i], i); + } auto ptr = std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); _modelMeshRenderItems << std::static_pointer_cast(ptr); auto material = getGeometry()->getShapeMaterial(shapeID); @@ -93,6 +97,7 @@ void CauterizedModel::createRenderItemSet() { shapeID++; } } + _blendedVertexBuffersInitialized = true; } else { Model::createRenderItemSet(); } @@ -170,9 +175,10 @@ void CauterizedModel::updateClusterMatrices() { } // post the blender if we're not currently waiting for one to finish - if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + auto modelBlender = DependencyManager::get(); + if (_blendedVertexBuffersInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; - DependencyManager::get()->noteRequiresBlend(getThisPointer()); + modelBlender->noteRequiresBlend(getThisPointer()); } } diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index cab95e50be..0e61b36280 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -38,33 +38,6 @@ void DebugDeferredBufferConfig::setMode(int newMode) { emit dirty(); } -#if 0 -enum TextureSlot { - Albedo = 0, - Normal, - Specular, - Depth, - Lighting, - Shadow, - LinearDepth, - HalfLinearDepth, - HalfNormal, - Curvature, - DiffusedCurvature, - Scattering, - AmbientOcclusion, - AmbientOcclusionBlurred, - Velocity, -}; - -enum ParamSlot { - CameraCorrection = 0, - DeferredFrameTransform, - ShadowTransform, - DebugParametersBuffer -}; -#endif - static const std::string DEFAULT_ALBEDO_SHADER{ "vec4 getFragmentColor() {" " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" @@ -178,19 +151,19 @@ static const std::string DEFAULT_SHADOW_CASCADE_SHADER{ static const std::string DEFAULT_LINEAR_DEPTH_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec3(1.0 - texture(linearDepthMap, uv).x * 0.01), 1.0);" + " return vec4(vec3(1.0 - texture(debugTexture0, uv).x * 0.01), 1.0);" "}" }; static const std::string DEFAULT_HALF_LINEAR_DEPTH_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec3(1.0 - texture(halfLinearDepthMap, uv).x * 0.01), 1.0);" + " return vec4(vec3(1.0 - texture(debugTexture0, uv).x * 0.01), 1.0);" " }" }; static const std::string DEFAULT_HALF_NORMAL_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec3(texture(halfNormalMap, uv).xyz), 1.0);" + " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);" " }" }; @@ -240,27 +213,27 @@ static const std::string DEFAULT_CURVATURE_OCCLUSION_SHADER{ static const std::string DEFAULT_DEBUG_SCATTERING_SHADER{ "vec4 getFragmentColor() {" - " return vec4(pow(vec3(texture(scatteringMap, uv).xyz), vec3(1.0 / 2.2)), 1.0);" - // " return vec4(vec3(texture(scatteringMap, uv).xyz), 1.0);" + " return vec4(pow(vec3(texture(debugTexture0, uv).xyz), vec3(1.0 / 2.2)), 1.0);" + // " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);" " }" }; static const std::string DEFAULT_AMBIENT_OCCLUSION_SHADER{ "vec4 getFragmentColor() {" " return vec4(vec3(texture(obscuranceMap, uv).x), 1.0);" - // When drawing color " return vec4(vec3(texture(occlusionMap, uv).xyz), 1.0);" - // when drawing normal" return vec4(normalize(texture(occlusionMap, uv).xyz * 2.0 - vec3(1.0)), 1.0);" + // When drawing color " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);" + // when drawing normal" return vec4(normalize(texture(debugTexture0, uv).xyz * 2.0 - vec3(1.0)), 1.0);" " }" }; static const std::string DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec3(texture(occlusionBlurredMap, uv).xyz), 1.0);" + " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);" " }" }; static const std::string DEFAULT_VELOCITY_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec2(texture(velocityMap, uv).xy), 0.0, 1.0);" + " return vec4(vec2(texture(debugTexture0, uv).xy), 0.0, 1.0);" " }" }; @@ -463,13 +436,10 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(Textures::DeferredDepth, deferredFramebuffer->getPrimaryDepthTexture()); batch.setResourceTexture(Textures::DeferredLighting, deferredFramebuffer->getLightingTexture()); } - if (velocityFramebuffer) { - batch.setResourceTexture(Textures::DebugVelocity, velocityFramebuffer->getVelocityTexture()); + if (velocityFramebuffer && _mode == VelocityMode) { + batch.setResourceTexture(Textures::DebugTexture0, velocityFramebuffer->getVelocityTexture()); } - // FIXME can't find the corresponding buffer - // batch.setUniformBuffer(UBOs:: DebugParametersBuffer, _parameters); - auto lightStage = renderContext->_scene->getStage(); assert(lightStage); assert(lightStage->getNumLights() > 0); @@ -479,12 +449,17 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(Textures::Shadow, globalShadow->map); batch.setUniformBuffer(UBOs::ShadowParams, globalShadow->getBuffer()); batch.setUniformBuffer(UBOs::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(UBOs::DebugDeferredParams, _parameters); } if (linearDepthTarget) { - batch.setResourceTexture(Textures::DebugDepth, linearDepthTarget->getLinearDepthTexture()); - batch.setResourceTexture(Textures::DebugHalfDepth, linearDepthTarget->getHalfLinearDepthTexture()); - batch.setResourceTexture(Textures::DebugHalfNormal, linearDepthTarget->getHalfNormalTexture()); + if (_mode == DepthMode) { + batch.setResourceTexture(Textures::DebugTexture0, linearDepthTarget->getLinearDepthTexture()); + } else if (_mode == HalfLinearDepthMode) { + batch.setResourceTexture(Textures::DebugTexture0, linearDepthTarget->getHalfLinearDepthTexture()); + } else if (_mode == HalfNormalMode) { + batch.setResourceTexture(Textures::DebugTexture0, linearDepthTarget->getHalfNormalTexture()); + } } if (surfaceGeometryFramebuffer) { batch.setResourceTexture(Textures::DeferredCurvature, surfaceGeometryFramebuffer->getCurvatureTexture()); @@ -492,9 +467,11 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I surfaceGeometryFramebuffer->getLowCurvatureTexture()); } if (ambientOcclusionFramebuffer) { - batch.setResourceTexture(Textures::DebugOcclusion, ambientOcclusionFramebuffer->getOcclusionTexture()); - batch.setResourceTexture(Textures::DebugOcclusionBlurred, - ambientOcclusionFramebuffer->getOcclusionBlurredTexture()); + if (_mode == AmbientOcclusionMode) { + batch.setResourceTexture(Textures::DebugTexture0, ambientOcclusionFramebuffer->getOcclusionTexture()); + } else if (_mode == AmbientOcclusionBlurredMode) { + batch.setResourceTexture(Textures::DebugTexture0, ambientOcclusionFramebuffer->getOcclusionBlurredTexture()); + } } const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); const glm::vec2 bottomLeft(_size.x, _size.y); @@ -505,19 +482,14 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(Textures::DeferredNormal, nullptr); batch.setResourceTexture(Textures::DeferredSpecular, nullptr); batch.setResourceTexture(Textures::DeferredDepth, nullptr); - batch.setResourceTexture(Textures::DeferredLighting, nullptr); - batch.setResourceTexture(Textures::Shadow, nullptr); - batch.setResourceTexture(Textures::DebugDepth, nullptr); - batch.setResourceTexture(Textures::DebugHalfDepth, nullptr); - batch.setResourceTexture(Textures::DebugHalfNormal, nullptr); - batch.setResourceTexture(Textures::DeferredCurvature, nullptr); batch.setResourceTexture(Textures::DeferredDiffusedCurvature, nullptr); + batch.setResourceTexture(Textures::DeferredLighting, nullptr); - batch.setResourceTexture(Textures::DebugOcclusion, nullptr); - batch.setResourceTexture(Textures::DebugOcclusionBlurred, nullptr); - - batch.setResourceTexture(Textures::DebugVelocity, nullptr); + for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { + batch.setResourceTexture(Textures::Shadow + i, nullptr); + } + batch.setResourceTexture(Textures::DebugTexture0, nullptr); }); } diff --git a/libraries/render-utils/src/DeferredBufferRead.slh b/libraries/render-utils/src/DeferredBufferRead.slh index 8c6c76b24a..e5a7c39d54 100644 --- a/libraries/render-utils/src/DeferredBufferRead.slh +++ b/libraries/render-utils/src/DeferredBufferRead.slh @@ -137,7 +137,7 @@ vec4 unpackDeferredPosition(float depthValue, vec2 texcoord) { // This method to unpack position is fastesst vec4 unpackDeferredPositionFromZdb(vec2 texcoord) { float Zdb = texture(depthMap, texcoord).x; - return unpackDeferredPosition(Zdb, texcoord); + return unpackDeferredPosition(Zdb, texcoord); } vec4 unpackDeferredPositionFromZeye(vec2 texcoord) { diff --git a/libraries/render-utils/src/DeferredFramebuffer.cpp b/libraries/render-utils/src/DeferredFramebuffer.cpp index 2df89b8808..1906375654 100644 --- a/libraries/render-utils/src/DeferredFramebuffer.cpp +++ b/libraries/render-utils/src/DeferredFramebuffer.cpp @@ -73,7 +73,7 @@ void DeferredFramebuffer::allocate() { _deferredFramebufferDepthColor->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); - auto smoothSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT); + auto smoothSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT, gpu::Sampler::WRAP_CLAMP); _lightingTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10), width, height, gpu::Texture::SINGLE_MIP, smoothSampler); _lightingFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("lighting")); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index af5a3f3e63..3b23711a64 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -32,7 +32,6 @@ namespace ru { using render_utils::slot::texture::Texture; using render_utils::slot::buffer::Buffer; - using render_utils::slot::uniform::Uniform; } namespace gr { diff --git a/libraries/render-utils/src/DeferredTransform.slh b/libraries/render-utils/src/DeferredTransform.slh index 6dd92c651b..19986805f6 100644 --- a/libraries/render-utils/src/DeferredTransform.slh +++ b/libraries/render-utils/src/DeferredTransform.slh @@ -38,8 +38,8 @@ struct DeferredFrameTransform { mat4 _projectionMono; mat4 _viewInverse; mat4 _view; - mat4 _projectionUnJittered[2]; - mat4 _invProjectionUnJittered[2]; + mat4 _projectionUnJittered[2]; + mat4 _invProjectionUnJittered[2]; }; layout(binding=RENDER_UTILS_BUFFER_DEFERRED_FRAME_TRANSFORM) uniform deferredFrameTransformBuffer { @@ -68,10 +68,10 @@ mat4 getProjectionMono() { return frameTransform._projectionMono; } mat4 getUnjitteredProjection(int side) { - return frameTransform._projectionUnJittered[side]; + return frameTransform._projectionUnJittered[side]; } mat4 getUnjitteredInvProjection(int side) { - return frameTransform._invProjectionUnJittered[side]; + return frameTransform._invProjectionUnJittered[side]; } // positive near distance of the projection @@ -158,7 +158,7 @@ vec3 evalUnjitteredEyePositionFromZdb(int side, float Zdb, vec2 texcoord) { } vec3 evalEyePositionFromZeye(int side, float Zeye, vec2 texcoord) { - float Zdb = evalZdbFromZeye(Zeye); + float Zdb = evalZdbFromZeye(Zeye); return evalEyePositionFromZdb(side, Zdb, texcoord); } diff --git a/libraries/render-utils/src/Fade_shared.slh b/libraries/render-utils/src/Fade_shared.slh index 7d1c0fb8bc..7ede92b807 100644 --- a/libraries/render-utils/src/Fade_shared.slh +++ b/libraries/render-utils/src/Fade_shared.slh @@ -13,12 +13,12 @@ struct FadeParameters { - VEC4 _noiseInvSizeAndLevel; - VEC4 _innerEdgeColor; - VEC4 _outerEdgeColor; - VEC2 _edgeWidthInvWidth; - FLOAT32 _baseLevel; - INT32 _isInverted; + VEC4 _noiseInvSizeAndLevel; + VEC4 _innerEdgeColor; + VEC4 _outerEdgeColor; + VEC2 _edgeWidthInvWidth; + FLOAT32 _baseLevel; + INT32 _isInverted; }; // <@if 1@> diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index cbc9b40129..2fe811c97b 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -2104,9 +2104,7 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { auto stateNoBlend = std::make_shared(); PrepareStencil::testMaskDrawShape(*stateNoBlend); - auto noBlendPS = gpu::Shader::createVertex(shader::gpu::fragment::DrawTextureOpaque); auto programNoBlend = gpu::Shader::createProgram(shader::render_utils::program::standardDrawTextureNoBlend); - _standardDrawPipelineNoBlend = gpu::Pipeline::create(programNoBlend, stateNoBlend); }); diff --git a/libraries/render-utils/src/Highlight.slh b/libraries/render-utils/src/Highlight.slh index 56a999f508..b26337676f 100644 --- a/libraries/render-utils/src/Highlight.slh +++ b/libraries/render-utils/src/Highlight.slh @@ -15,15 +15,15 @@ <@include Highlight_shared.slh@> -layout(binding=RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS) uniform highlightParamsBuffer { +layout(std140, binding=RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS) uniform highlightParamsBuffer { HighlightParameters params; }; layout(binding=RENDER_UTILS_TEXTURE_HIGHLIGHT_SCENE_DEPTH) uniform sampler2D sceneDepthMap; layout(binding=RENDER_UTILS_TEXTURE_HIGHLIGHT_DEPTH) uniform sampler2D highlightedDepthMap; -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; const float FAR_Z = 1.0; const float LINEAR_DEPTH_BIAS = 5e-3; @@ -55,10 +55,10 @@ void main(void) { discard; <@endif@> } else { - vec2 halfTexel = getInvWidthHeight() / 2; + vec2 halfTexel = getInvWidthHeight() / 2.0; vec2 texCoord0 = varTexCoord0+halfTexel; float weight = 0.0; - vec2 deltaUv = params._size / params._blurKernelSize; + vec2 deltaUv = params._size / float(params._blurKernelSize); vec2 lineStartUv = texCoord0 - params._size / 2.0; vec2 uv; int x; @@ -87,7 +87,7 @@ void main(void) { } } - if (intensity > 0) { + if (intensity > 0.0) { // sumOutlineDepth /= intensity; } else { sumOutlineDepth = FAR_Z; diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 7e2e55c768..bcac31dd5a 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -28,7 +28,6 @@ using namespace render; namespace ru { using render_utils::slot::texture::Texture; using render_utils::slot::buffer::Buffer; - using render_utils::slot::uniform::Uniform; } namespace gr { @@ -38,6 +37,8 @@ namespace gr { #define OUTLINE_STENCIL_MASK 1 +extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); + HighlightRessources::HighlightRessources() { } @@ -181,6 +182,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c auto maskPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); auto maskSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); + auto maskSkinnedDQPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned().withDualQuatSkinned()); // Setup camera, projection and viewport for all items batch.setViewportTransform(args->_viewport); @@ -188,14 +190,17 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c batch.setProjectionJitter(jitter.x, jitter.y); batch.setViewTransform(viewMat); - std::vector skinnedShapeKeys{}; + std::vector skinnedShapeKeys; + std::vector skinnedDQShapeKeys; // Iterate through all inShapes and render the unskinned args->_shapePipeline = maskPipeline; batch.setPipeline(maskPipeline->pipeline); for (const auto& items : inShapes) { itemBounds.insert(itemBounds.end(), items.second.begin(), items.second.end()); - if (items.first.isSkinned()) { + if (items.first.isSkinned() && items.first.isDualQuatSkinned()) { + skinnedDQShapeKeys.push_back(items.first); + } else if (items.first.isSkinned()) { skinnedShapeKeys.push_back(items.first); } else { renderItems(renderContext, items.second); @@ -203,10 +208,21 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c } // Reiterate to render the skinned - args->_shapePipeline = maskSkinnedPipeline; - batch.setPipeline(maskSkinnedPipeline->pipeline); - for (const auto& key : skinnedShapeKeys) { - renderItems(renderContext, inShapes.at(key)); + if (skinnedShapeKeys.size() > 0) { + args->_shapePipeline = maskSkinnedPipeline; + batch.setPipeline(maskSkinnedPipeline->pipeline); + for (const auto& key : skinnedShapeKeys) { + renderItems(renderContext, inShapes.at(key)); + } + } + + // Reiterate to render the DQ skinned + if (skinnedDQShapeKeys.size() > 0) { + args->_shapePipeline = maskSkinnedDQPipeline; + batch.setPipeline(maskSkinnedDQPipeline->pipeline); + for (const auto& key : skinnedDQShapeKeys) { + renderItems(renderContext, inShapes.at(key)); + } } args->_shapePipeline = nullptr; @@ -489,7 +505,7 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setColorWriteMask(false, false, false, false); - initMaskPipelines(*shapePlumber, state); + initZPassPipelines(*shapePlumber, state); } auto sharedParameters = std::make_shared(); @@ -549,16 +565,4 @@ const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const const auto selectedMetasAndOpaques = task.addJob("OpaqueSelection", selectMetaAndOpaqueInput); const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, selectionName).asVarying(); return task.addJob("TransparentSelection", selectItemInput); -} - -void DrawHighlightTask::initMaskPipelines(render::ShapePlumber& shapePlumber, gpu::StatePointer state) { - gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(shader::render_utils::program::model_shadow); - shapePlumber.addPipeline( - ShapeKey::Filter::Builder().withoutSkinned(), - modelProgram, state); - - gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(shader::render_utils::program::skin_model_shadow); - shapePlumber.addPipeline( - ShapeKey::Filter::Builder().withSkinned(), - skinProgram, state); -} +} \ No newline at end of file diff --git a/libraries/render-utils/src/HighlightEffect.h b/libraries/render-utils/src/HighlightEffect.h index 64a97a549e..32668c1ab6 100644 --- a/libraries/render-utils/src/HighlightEffect.h +++ b/libraries/render-utils/src/HighlightEffect.h @@ -208,8 +208,6 @@ public: void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); private: - - static void initMaskPipelines(render::ShapePlumber& plumber, gpu::StatePointer state); static const render::Varying addSelectItemJobs(JobModel& task, const render::Varying& selectionName, const RenderFetchCullSortTask::BucketList& items); }; diff --git a/libraries/render-utils/src/Highlight_aabox.slv b/libraries/render-utils/src/Highlight_aabox.slv index 5130d5e7ff..2ecebdea51 100644 --- a/libraries/render-utils/src/Highlight_aabox.slv +++ b/libraries/render-utils/src/Highlight_aabox.slv @@ -45,7 +45,7 @@ struct HighlightParameters { vec2 outlineWidth; }; -layout(binding=0) uniform parametersBuffer { +layout(std140, binding=0) uniform parametersBuffer { HighlightParameters _parameters; }; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index ca6e736a65..94a5026fb4 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -208,7 +208,9 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in bool useDualQuaternionSkinning = model->getUseDualQuaternionSkinning(); - _blendedVertexBuffer = model->_blendedVertexBuffers[_meshIndex]; + if (!model->getFBXGeometry().meshes[meshIndex].blendshapes.isEmpty()) { + _blendedVertexBuffer = model->_blendedVertexBuffers[meshIndex]; + } auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex); const Model::MeshState& state = model->getMeshState(_meshIndex); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 8f4eb57f8e..ab6507b29c 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -42,8 +42,9 @@ using namespace std; int nakedModelPointerTypeId = qRegisterMetaType(); -int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType(); -int vec3VectorTypeId = qRegisterMetaType >(); +int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType(); +int vec3VectorTypeId = qRegisterMetaType>(); +int normalTypeVecTypeId = qRegisterMetaType>("QVector"); float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; #define HTTP_INVALID_COM "http://invalid.com" @@ -301,45 +302,22 @@ bool Model::updateGeometry() { assert(_meshStates.empty()); const FBXGeometry& fbxGeometry = getFBXGeometry(); + int i = 0; foreach (const FBXMesh& mesh, fbxGeometry.meshes) { MeshState state; state.clusterDualQuaternions.resize(mesh.clusters.size()); state.clusterMatrices.resize(mesh.clusters.size()); _meshStates.push_back(state); - - // Note: we add empty buffers for meshes that lack blendshapes so we can access the buffers by index - // later in ModelMeshPayload, however the vast majority of meshes will not have them. - // TODO? make _blendedVertexBuffers a map instead of vector and only add for meshes with blendshapes? - auto buffer = std::make_shared(); - if (!mesh.blendshapes.isEmpty()) { - std::vector normalsAndTangents; - normalsAndTangents.reserve(mesh.normals.size() + mesh.tangents.size()); - - for (auto normalIt = mesh.normals.begin(), tangentIt = mesh.tangents.begin(); - normalIt != mesh.normals.end(); - ++normalIt, ++tangentIt) { -#if FBX_PACK_NORMALS - glm::uint32 finalNormal; - glm::uint32 finalTangent; - buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); -#else - const auto finalNormal = *normalIt; - const auto finalTangent = *tangentIt; -#endif - normalsAndTangents.push_back(finalNormal); - normalsAndTangents.push_back(finalTangent); - } - - buffer->resize(mesh.vertices.size() * (sizeof(glm::vec3) + 2 * sizeof(NormalType))); - buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (const gpu::Byte*) mesh.vertices.constData()); - buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3), - mesh.normals.size() * 2 * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); + if (!mesh.blendshapes.empty() && _blendedVertexBuffers.find(i) == _blendedVertexBuffers.end()) { + initializeBlendshapes(mesh, i); } - _blendedVertexBuffers.push_back(buffer); + i++; } + _blendedVertexBuffersInitialized = true; needFullUpdate = true; emit rigReady(); } + return needFullUpdate; } @@ -376,17 +354,20 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g // we can use the AABox's intersection by mapping our origin and direction into the model frame // and testing intersection there. - if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, distance, face, surfaceNormal)) { + if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, 1.0f / modelFrameDirection, distance, face, surfaceNormal)) { QMutexLocker locker(&_mutex); - float bestDistance = std::numeric_limits::max(); + float bestDistance = FLT_MAX; + BoxFace bestFace; Triangle bestModelTriangle; Triangle bestWorldTriangle; + glm::vec3 bestWorldIntersectionPoint; + glm::vec3 bestMeshIntersectionPoint; + int bestPartIndex = 0; + int bestShapeID = 0; int bestSubMeshIndex = 0; - int subMeshIndex = 0; const FBXGeometry& geometry = getFBXGeometry(); - if (!_triangleSetsValid) { calculateTriangleSets(geometry); } @@ -397,41 +378,78 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); glm::vec3 meshFrameDirection = glm::vec3(worldToMeshMatrix * glm::vec4(direction, 0.0f)); + glm::vec3 meshFrameInvDirection = 1.0f / meshFrameDirection; int shapeID = 0; + int subMeshIndex = 0; + + std::vector sortedTriangleSets; for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { int partIndex = 0; - for (auto &partTriangleSet : meshTriangleSets) { - float triangleSetDistance; - BoxFace triangleSetFace; - Triangle triangleSetTriangle; - if (partTriangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { - glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); - glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); - float worldDistance = glm::distance(origin, worldIntersectionPoint); - - if (worldDistance < bestDistance) { - bestDistance = worldDistance; - intersectedSomething = true; - face = triangleSetFace; - bestModelTriangle = triangleSetTriangle; - bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; - extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); - extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); - extraInfo["partIndex"] = partIndex; - extraInfo["shapeID"] = shapeID; - bestSubMeshIndex = subMeshIndex; + for (auto& partTriangleSet : meshTriangleSets) { + float priority = FLT_MAX; + if (partTriangleSet.getBounds().contains(meshFrameOrigin)) { + priority = 0.0f; + } else { + float partBoundDistance = FLT_MAX; + BoxFace partBoundFace; + glm::vec3 partBoundNormal; + if (partTriangleSet.getBounds().findRayIntersection(meshFrameOrigin, meshFrameDirection, meshFrameInvDirection, + partBoundDistance, partBoundFace, partBoundNormal)) { + priority = partBoundDistance; } } + + if (priority < FLT_MAX) { + sortedTriangleSets.emplace_back(priority, &partTriangleSet, partIndex, shapeID, subMeshIndex); + } partIndex++; shapeID++; } subMeshIndex++; } + if (sortedTriangleSets.size() > 1) { + static auto comparator = [](const SortedTriangleSet& left, const SortedTriangleSet& right) { return left.distance < right.distance; }; + std::sort(sortedTriangleSets.begin(), sortedTriangleSets.end(), comparator); + } + + for (auto it = sortedTriangleSets.begin(); it != sortedTriangleSets.end(); ++it) { + const SortedTriangleSet& sortedTriangleSet = *it; + // We can exit once triangleSetDistance > bestDistance + if (sortedTriangleSet.distance > bestDistance) { + break; + } + float triangleSetDistance = FLT_MAX; + BoxFace triangleSetFace; + Triangle triangleSetTriangle; + if (sortedTriangleSet.triangleSet->findRayIntersection(meshFrameOrigin, meshFrameDirection, meshFrameInvDirection, triangleSetDistance, triangleSetFace, + triangleSetTriangle, pickAgainstTriangles, allowBackface)) { + if (triangleSetDistance < bestDistance) { + bestDistance = triangleSetDistance; + intersectedSomething = true; + bestFace = triangleSetFace; + bestModelTriangle = triangleSetTriangle; + bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; + glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); + glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); + bestWorldIntersectionPoint = worldIntersectionPoint; + bestMeshIntersectionPoint = meshIntersectionPoint; + bestPartIndex = sortedTriangleSet.partIndex; + bestShapeID = sortedTriangleSet.shapeID; + bestSubMeshIndex = sortedTriangleSet.subMeshIndex; + } + } + } + if (intersectedSomething) { distance = bestDistance; + face = bestFace; surfaceNormal = bestWorldTriangle.getNormal(); + extraInfo["worldIntersectionPoint"] = vec3toVariant(bestWorldIntersectionPoint); + extraInfo["meshIntersectionPoint"] = vec3toVariant(bestMeshIntersectionPoint); + extraInfo["partIndex"] = bestPartIndex; + extraInfo["shapeID"] = bestShapeID; if (pickAgainstTriangles) { extraInfo["subMeshIndex"] = bestSubMeshIndex; extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex); @@ -483,13 +501,16 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co QMutexLocker locker(&_mutex); float bestDistance = FLT_MAX; + BoxFace bestFace; Triangle bestModelTriangle; Triangle bestWorldTriangle; + glm::vec3 bestWorldIntersectionPoint; + glm::vec3 bestMeshIntersectionPoint; + int bestPartIndex = 0; + int bestShapeID = 0; int bestSubMeshIndex = 0; - int subMeshIndex = 0; const FBXGeometry& geometry = getFBXGeometry(); - if (!_triangleSetsValid) { calculateTriangleSets(geometry); } @@ -503,40 +524,79 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co glm::vec3 meshFrameAcceleration = glm::vec3(worldToMeshMatrix * glm::vec4(acceleration, 0.0f)); int shapeID = 0; + int subMeshIndex = 0; + + std::vector sortedTriangleSets; for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { int partIndex = 0; - for (auto &partTriangleSet : meshTriangleSets) { - float triangleSetDistance; - BoxFace triangleSetFace; - Triangle triangleSetTriangle; - if (partTriangleSet.findParabolaIntersection(meshFrameOrigin, meshFrameVelocity, meshFrameAcceleration, - triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { - if (triangleSetDistance < bestDistance) { - bestDistance = triangleSetDistance; - intersectedSomething = true; - face = triangleSetFace; - bestModelTriangle = triangleSetTriangle; - bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; - glm::vec3 meshIntersectionPoint = meshFrameOrigin + meshFrameVelocity * triangleSetDistance + - 0.5f * meshFrameAcceleration * triangleSetDistance * triangleSetDistance; - glm::vec3 worldIntersectionPoint = origin + velocity * triangleSetDistance + - 0.5f * acceleration * triangleSetDistance * triangleSetDistance; - extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); - extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); - extraInfo["partIndex"] = partIndex; - extraInfo["shapeID"] = shapeID; - bestSubMeshIndex = subMeshIndex; + for (auto& partTriangleSet : meshTriangleSets) { + float priority = FLT_MAX; + if (partTriangleSet.getBounds().contains(meshFrameOrigin)) { + priority = 0.0f; + } else { + float partBoundDistance = FLT_MAX; + BoxFace partBoundFace; + glm::vec3 partBoundNormal; + if (partTriangleSet.getBounds().findParabolaIntersection(meshFrameOrigin, meshFrameVelocity, meshFrameAcceleration, + partBoundDistance, partBoundFace, partBoundNormal)) { + priority = partBoundDistance; } } + + if (priority < FLT_MAX) { + sortedTriangleSets.emplace_back(priority, &partTriangleSet, partIndex, shapeID, subMeshIndex); + } partIndex++; shapeID++; } subMeshIndex++; } + if (sortedTriangleSets.size() > 1) { + static auto comparator = [](const SortedTriangleSet& left, const SortedTriangleSet& right) { return left.distance < right.distance; }; + std::sort(sortedTriangleSets.begin(), sortedTriangleSets.end(), comparator); + } + + for (auto it = sortedTriangleSets.begin(); it != sortedTriangleSets.end(); ++it) { + const SortedTriangleSet& sortedTriangleSet = *it; + // We can exit once triangleSetDistance > bestDistance + if (sortedTriangleSet.distance > bestDistance) { + break; + } + float triangleSetDistance = FLT_MAX; + BoxFace triangleSetFace; + Triangle triangleSetTriangle; + if (sortedTriangleSet.triangleSet->findParabolaIntersection(meshFrameOrigin, meshFrameVelocity, meshFrameAcceleration, + triangleSetDistance, triangleSetFace, triangleSetTriangle, + pickAgainstTriangles, allowBackface)) { + if (triangleSetDistance < bestDistance) { + bestDistance = triangleSetDistance; + intersectedSomething = true; + bestFace = triangleSetFace; + bestModelTriangle = triangleSetTriangle; + bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; + glm::vec3 meshIntersectionPoint = meshFrameOrigin + meshFrameVelocity * triangleSetDistance + + 0.5f * meshFrameAcceleration * triangleSetDistance * triangleSetDistance; + glm::vec3 worldIntersectionPoint = origin + velocity * triangleSetDistance + + 0.5f * acceleration * triangleSetDistance * triangleSetDistance; + bestWorldIntersectionPoint = worldIntersectionPoint; + bestMeshIntersectionPoint = meshIntersectionPoint; + bestPartIndex = sortedTriangleSet.partIndex; + bestShapeID = sortedTriangleSet.shapeID; + bestSubMeshIndex = sortedTriangleSet.subMeshIndex; + // These sets can overlap, so we can't exit early if we find something + } + } + } + if (intersectedSomething) { parabolicDistance = bestDistance; + face = bestFace; surfaceNormal = bestWorldTriangle.getNormal(); + extraInfo["worldIntersectionPoint"] = vec3toVariant(bestWorldIntersectionPoint); + extraInfo["meshIntersectionPoint"] = vec3toVariant(bestMeshIntersectionPoint); + extraInfo["partIndex"] = bestPartIndex; + extraInfo["shapeID"] = bestShapeID; if (pickAgainstTriangles) { extraInfo["subMeshIndex"] = bestSubMeshIndex; extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex); @@ -974,6 +1034,10 @@ void Model::removeFromScene(const render::ScenePointer& scene, render::Transacti _modelMeshMaterialNames.clear(); _modelMeshRenderItemShapes.clear(); + _blendedVertexBuffers.clear(); + _normalsAndTangents.clear(); + _blendedVertexBuffersInitialized = false; + _addedToScene = false; _renderInfoVertexCount = 0; @@ -1220,8 +1284,7 @@ QStringList Model::getJointNames() const { class Blender : public QRunnable { public: - Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& meshes, const QVector& blendshapeCoefficients); + Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& blendshapeCoefficients); virtual void run() override; @@ -1230,35 +1293,37 @@ private: ModelPointer _model; int _blendNumber; Geometry::WeakPointer _geometry; - QVector _meshes; QVector _blendshapeCoefficients; }; -Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& meshes, const QVector& blendshapeCoefficients) : +Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& blendshapeCoefficients) : _model(model), _blendNumber(blendNumber), _geometry(geometry), - _meshes(meshes), _blendshapeCoefficients(blendshapeCoefficients) { } void Blender::run() { - DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } }); - QVector vertices, normals, tangents; - if (_model) { + QVector vertices; + QVector normalsAndTangents; + if (_model && _model->isLoaded()) { + DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } }); int offset = 0; - foreach (const FBXMesh& mesh, _meshes) { - if (mesh.blendshapes.isEmpty()) { + int normalsAndTangentsOffset = 0; + auto meshes = _model->getFBXGeometry().meshes; + int meshIndex = 0; + foreach (const FBXMesh& mesh, meshes) { + auto modelMeshNormalsAndTangents = _model->_normalsAndTangents.find(meshIndex++); + if (mesh.blendshapes.isEmpty() || modelMeshNormalsAndTangents == _model->_normalsAndTangents.end()) { continue; } + vertices += mesh.vertices; - normals += mesh.normals; - tangents += mesh.tangents; + normalsAndTangents += modelMeshNormalsAndTangents->second; glm::vec3* meshVertices = vertices.data() + offset; - glm::vec3* meshNormals = normals.data() + offset; - glm::vec3* meshTangents = tangents.data() + offset; + NormalType* meshNormalsAndTangents = normalsAndTangents.data() + normalsAndTangentsOffset; offset += mesh.vertices.size(); + normalsAndTangentsOffset += modelMeshNormalsAndTangents->second.size(); const float NORMAL_COEFFICIENT_SCALE = 0.01f; for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) { float vertexCoefficient = _blendshapeCoefficients.at(i); @@ -1268,22 +1333,38 @@ void Blender::run() { } float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE; const FBXBlendshape& blendshape = mesh.blendshapes.at(i); - for (int j = 0; j < blendshape.indices.size(); j++) { - int index = blendshape.indices.at(j); - meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; - meshNormals[index] += blendshape.normals.at(j) * normalCoefficient; - if (blendshape.tangents.size() > j) { - meshTangents[index] += blendshape.tangents.at(j) * normalCoefficient; + tbb::parallel_for(tbb::blocked_range(0, blendshape.indices.size()), [&](const tbb::blocked_range& range) { + for (auto j = range.begin(); j < range.end(); j++) { + int index = blendshape.indices.at(j); + meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; + + glm::vec3 normal = mesh.normals.at(index) + blendshape.normals.at(j) * normalCoefficient; + glm::vec3 tangent; + if (index < mesh.tangents.size()) { + tangent = mesh.tangents.at(index); + if ((int)j < blendshape.tangents.size()) { + tangent += blendshape.tangents.at(j) * normalCoefficient; + } + } +#if FBX_PACK_NORMALS + glm::uint32 finalNormal; + glm::uint32 finalTangent; + buffer_helpers::packNormalAndTangent(normal, tangent, finalNormal, finalTangent); +#else + const auto& finalNormal = normal; + const auto& finalTangent = tangent; +#endif + meshNormalsAndTangents[2 * index] = finalNormal; + meshNormalsAndTangents[2 * index + 1] = finalTangent; } - } + }); } } } - // post the result to the geometry cache, which will dispatch to the model if still alive + // post the result to the ModelBlender, which will dispatch to the model if still alive QMetaObject::invokeMethod(DependencyManager::get().data(), "setBlendedVertices", - Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), - Q_ARG(const Geometry::WeakPointer&, _geometry), Q_ARG(const QVector&, vertices), - Q_ARG(const QVector&, normals), Q_ARG(const QVector&, tangents)); + Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), Q_ARG(QVector, vertices), + Q_ARG(QVector, normalsAndTangents)); } void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) { @@ -1443,9 +1524,10 @@ void Model::updateClusterMatrices() { } // post the blender if we're not currently waiting for one to finish - if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + auto modelBlender = DependencyManager::get(); + if (_blendedVertexBuffersInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; - DependencyManager::get()->noteRequiresBlend(getThisPointer()); + modelBlender->noteRequiresBlend(getThisPointer()); } } @@ -1453,98 +1535,45 @@ bool Model::maybeStartBlender() { if (isLoaded()) { const FBXGeometry& fbxGeometry = getFBXGeometry(); if (fbxGeometry.hasBlendedMeshes()) { - QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _renderGeometry, - fbxGeometry.meshes, _blendshapeCoefficients)); + QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _renderGeometry, _blendshapeCoefficients)); return true; } } return false; } -void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals, const QVector& tangents) { - auto geometryRef = geometry.lock(); - if (!geometryRef || _renderGeometry != geometryRef || _blendedVertexBuffers.empty() || blendNumber < _appliedBlendNumber) { +void Model::setBlendedVertices(int blendNumber, const QVector& vertices, const QVector& normalsAndTangents) { + if (!isLoaded() || blendNumber < _appliedBlendNumber || !_blendedVertexBuffersInitialized) { return; } _appliedBlendNumber = blendNumber; const FBXGeometry& fbxGeometry = getFBXGeometry(); int index = 0; - std::vector normalsAndTangents; + int normalAndTangentIndex = 0; for (int i = 0; i < fbxGeometry.meshes.size(); i++) { const FBXMesh& mesh = fbxGeometry.meshes.at(i); - if (mesh.blendshapes.isEmpty()) { + auto meshNormalsAndTangents = _normalsAndTangents.find(i); + const auto& buffer = _blendedVertexBuffers.find(i); + if (mesh.blendshapes.isEmpty() || meshNormalsAndTangents == _normalsAndTangents.end() || buffer == _blendedVertexBuffers.end()) { continue; } - gpu::BufferPointer& buffer = _blendedVertexBuffers[i]; const auto vertexCount = mesh.vertices.size(); const auto verticesSize = vertexCount * sizeof(glm::vec3); - const auto offset = index * sizeof(glm::vec3); - - normalsAndTangents.clear(); - normalsAndTangents.resize(normals.size()+tangents.size()); -// assert(normalsAndTangents.size() == 2 * vertexCount); - - // Interleave normals and tangents -#if 0 - // Sequential version for debugging - auto normalsRange = std::make_pair(normals.begin() + index, normals.begin() + index + vertexCount); - auto tangentsRange = std::make_pair(tangents.begin() + index, tangents.begin() + index + vertexCount); - auto normalsAndTangentsIt = normalsAndTangents.begin(); - - for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; - normalIt != normalsRange.second; - ++normalIt, ++tangentIt) { -#if FBX_PACK_NORMALS - glm::uint32 finalNormal; - glm::uint32 finalTangent; - buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); -#else - const auto finalNormal = *normalIt; - const auto finalTangent = *tangentIt; -#endif - *normalsAndTangentsIt = finalNormal; - ++normalsAndTangentsIt; - *normalsAndTangentsIt = finalTangent; - ++normalsAndTangentsIt; - } -#else - // Parallel version for performance - tbb::parallel_for(tbb::blocked_range(index, index+vertexCount), [&](const tbb::blocked_range& range) { - auto normalsRange = std::make_pair(normals.begin() + range.begin(), normals.begin() + range.end()); - auto tangentsRange = std::make_pair(tangents.begin() + range.begin(), tangents.begin() + range.end()); - auto normalsAndTangentsIt = normalsAndTangents.begin() + (range.begin()-index)*2; - - for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; - normalIt != normalsRange.second; - ++normalIt, ++tangentIt) { -#if FBX_PACK_NORMALS - glm::uint32 finalNormal; - glm::uint32 finalTangent; - buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); -#else - const auto finalNormal = *normalIt; - const auto finalTangent = *tangentIt; -#endif - *normalsAndTangentsIt = finalNormal; - ++normalsAndTangentsIt; - *normalsAndTangentsIt = finalTangent; - ++normalsAndTangentsIt; - } - }); -#endif - - buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + offset); - buffer->setSubData(verticesSize, 2 * vertexCount * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); + buffer->second->resize(mesh.vertices.size() * sizeof(glm::vec3) + meshNormalsAndTangents->second.size() * sizeof(NormalType)); + buffer->second->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3)); + buffer->second->setSubData(verticesSize, meshNormalsAndTangents->second.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType)); index += vertexCount; + normalAndTangentIndex += meshNormalsAndTangents->second.size(); } } void Model::deleteGeometry() { _deleteGeometryCounter++; _blendedVertexBuffers.clear(); + _normalsAndTangents.clear(); + _blendedVertexBuffersInitialized = false; _meshStates.clear(); _rig.destroyAnimGraph(); _blendedBlendshapeCoefficients.clear(); @@ -1576,6 +1605,42 @@ const render::ItemIDs& Model::fetchRenderItemIDs() const { return _modelMeshRenderItemIDs; } +void Model::initializeBlendshapes(const FBXMesh& mesh, int index) { + _blendedVertexBuffers[index] = std::make_shared(); + QVector normalsAndTangents; + normalsAndTangents.resize(2 * mesh.normals.size()); + + // Interleave normals and tangents + // Parallel version for performance + tbb::parallel_for(tbb::blocked_range(0, mesh.normals.size()), [&](const tbb::blocked_range& range) { + auto normalsRange = std::make_pair(mesh.normals.begin() + range.begin(), mesh.normals.begin() + range.end()); + auto tangentsRange = std::make_pair(mesh.tangents.begin() + range.begin(), mesh.tangents.begin() + range.end()); + auto normalsAndTangentsIt = normalsAndTangents.begin() + 2 * range.begin(); + + for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; + normalIt != normalsRange.second; + ++normalIt, ++tangentIt) { +#if FBX_PACK_NORMALS + glm::uint32 finalNormal; + glm::uint32 finalTangent; + buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); +#else + const auto& finalNormal = *normalIt; + const auto& finalTangent = *tangentIt; +#endif + *normalsAndTangentsIt = finalNormal; + ++normalsAndTangentsIt; + *normalsAndTangentsIt = finalTangent; + ++normalsAndTangentsIt; + } + }); + const auto verticesSize = mesh.vertices.size() * sizeof(glm::vec3); + _blendedVertexBuffers[index]->resize(mesh.vertices.size() * sizeof(glm::vec3) + normalsAndTangents.size() * sizeof(NormalType)); + _blendedVertexBuffers[index]->setSubData(0, verticesSize, (const gpu::Byte*) mesh.vertices.constData()); + _blendedVertexBuffers[index]->setSubData(verticesSize, normalsAndTangents.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); + _normalsAndTangents[index] = normalsAndTangents; +} + void Model::createRenderItemSet() { assert(isLoaded()); const auto& meshes = _renderGeometry->getMeshes(); @@ -1604,6 +1669,7 @@ void Model::createRenderItemSet() { // Run through all of the meshes, and place them into their segregated, but unsorted buckets int shapeID = 0; uint32_t numMeshes = (uint32_t)meshes.size(); + auto& fbxGeometry = getFBXGeometry(); for (uint32_t i = 0; i < numMeshes; i++) { const auto& mesh = meshes.at(i); if (!mesh) { @@ -1613,6 +1679,9 @@ void Model::createRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { + if (!fbxGeometry.meshes[i].blendshapes.empty() && _blendedVertexBuffers.find(i) == _blendedVertexBuffers.end()) { + initializeBlendshapes(fbxGeometry.meshes[i], i); + } _modelMeshRenderItems << std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); auto material = getGeometry()->getShapeMaterial(shapeID); _modelMeshMaterialNames.push_back(material ? material->getName() : ""); @@ -1620,6 +1689,7 @@ void Model::createRenderItemSet() { shapeID++; } } + _blendedVertexBuffersInitialized = true; } bool Model::isRenderable() const { @@ -1712,37 +1782,38 @@ ModelBlender::~ModelBlender() { } void ModelBlender::noteRequiresBlend(ModelPointer model) { + Lock lock(_mutex); if (_pendingBlenders < QThread::idealThreadCount()) { if (model->maybeStartBlender()) { _pendingBlenders++; + return; } - return; } - { - Lock lock(_mutex); - _modelsRequiringBlends.insert(model); - } + _modelsRequiringBlends.insert(model); } -void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals, - const QVector& tangents) { +void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, QVector vertices, QVector normalsAndTangents) { if (model) { - model->setBlendedVertices(blendNumber, geometry, vertices, normals, tangents); + model->setBlendedVertices(blendNumber, vertices, normalsAndTangents); } - _pendingBlenders--; { Lock lock(_mutex); - for (auto i = _modelsRequiringBlends.begin(); i != _modelsRequiringBlends.end();) { + _pendingBlenders--; + _modelsRequiringBlends.erase(model); + std::set> modelsToErase; + for (auto i = _modelsRequiringBlends.begin(); i != _modelsRequiringBlends.end(); i++) { auto weakPtr = *i; - _modelsRequiringBlends.erase(i++); // remove front of the set ModelPointer nextModel = weakPtr.lock(); if (nextModel && nextModel->maybeStartBlender()) { _pendingBlenders++; - return; + break; + } else { + modelsToErase.insert(weakPtr); } } + for (auto& weakPtr : modelsToErase) { + _modelsRequiringBlends.erase(weakPtr); + } } } - diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 627e5fddab..447f75dd9d 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -64,6 +64,16 @@ class Model; using ModelPointer = std::shared_ptr; using ModelWeakPointer = std::weak_ptr; +struct SortedTriangleSet { + SortedTriangleSet(float distance, TriangleSet* triangleSet, int partIndex, int shapeID, int subMeshIndex) : + distance(distance), triangleSet(triangleSet), partIndex(partIndex), shapeID(shapeID), subMeshIndex(subMeshIndex) {} + + float distance; + TriangleSet* triangleSet; + int partIndex; + int shapeID; + int subMeshIndex; +}; /// A generic 3D model displaying geometry loaded from a URL. class Model : public QObject, public std::enable_shared_from_this, public scriptable::ModelProvider { @@ -134,8 +144,7 @@ public: bool maybeStartBlender(); /// Sets blended vertices computed in a separate thread. - void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals, const QVector& tangents); + void setBlendedVertices(int blendNumber, const QVector& vertices, const QVector& normalsAndTangents); bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); } bool isAddedToScene() const { return _addedToScene; } @@ -335,6 +344,8 @@ public: void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); + std::unordered_map> _normalsAndTangents; + public slots: void loadURLFinished(bool success); @@ -413,9 +424,10 @@ protected: QUrl _url; - gpu::Buffers _blendedVertexBuffers; + std::unordered_map _blendedVertexBuffers; + bool _blendedVertexBuffersInitialized { false }; - QVector > > _dilatedTextures; + QVector>> _dilatedTextures; QVector _blendedBlendshapeCoefficients; int _blendNumber; @@ -482,6 +494,8 @@ protected: bool shouldInvalidatePayloadShapeKey(int meshIndex); + void initializeBlendshapes(const FBXMesh& mesh, int index); + private: float _loadingPriority { 0.0f }; @@ -503,9 +517,11 @@ public: /// Adds the specified model to the list requiring vertex blends. void noteRequiresBlend(ModelPointer model); + bool shouldComputeBlendshapes() { return _computeBlendshapes; } + public slots: - void setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals, const QVector& tangents); + void setBlendedVertices(ModelPointer model, int blendNumber, QVector vertices, QVector normalsAndTangents); + void setComputeBlendshapes(bool computeBlendshapes) { _computeBlendshapes = computeBlendshapes; } private: using Mutex = std::mutex; @@ -517,6 +533,8 @@ private: std::set> _modelsRequiringBlends; int _pendingBlenders; Mutex _mutex; + + bool _computeBlendshapes { true }; }; diff --git a/libraries/render-utils/src/PickItemsJob.cpp b/libraries/render-utils/src/PickItemsJob.cpp index 860262a969..f41222d358 100644 --- a/libraries/render-utils/src/PickItemsJob.cpp +++ b/libraries/render-utils/src/PickItemsJob.cpp @@ -33,6 +33,7 @@ void PickItemsJob::run(const render::RenderContextPointer& renderContext, const render::ItemBound PickItemsJob::findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const { const glm::vec3 rayOrigin = renderContext->args->getViewFrustum().getPosition(); const glm::vec3 rayDirection = renderContext->args->getViewFrustum().getDirection(); + const glm::vec3 rayInvDirection = 1.0f / rayDirection; BoxFace face; glm::vec3 normal; float isectDistance; @@ -42,7 +43,7 @@ render::ItemBound PickItemsJob::findNearestItem(const render::RenderContextPoint render::ItemKey itemKey; for (const auto& itemBound : inputs) { - if (!itemBound.bound.contains(rayOrigin) && itemBound.bound.findRayIntersection(rayOrigin, rayDirection, isectDistance, face, normal)) { + if (!itemBound.bound.contains(rayOrigin) && itemBound.bound.findRayIntersection(rayOrigin, rayDirection, rayInvDirection, isectDistance, face, normal)) { auto& item = renderContext->_scene->getItem(itemBound.id); itemKey = item.getKey(); if (itemKey.isWorldSpace() && isectDistance>minDistance && isectDistance < minIsectDistance && isectDistance("FetchShadowTree", fetchInput); - const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterReceiverFilter).asVarying(); - const auto shadowItems = task.addJob("FetchShadowSelection", selectionInputs); + const auto selectionInputs = FilterSpatialSelection::Inputs(shadowSelection, shadowCasterReceiverFilter).asVarying(); + const auto shadowItems = task.addJob("FilterShadowSelection", selectionInputs); // Cull objects that are not visible in camera view. Hopefully the cull functor only performs LOD culling, not // frustum culling or this will make shadow casters out of the camera frustum disappear. diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index 85a30d5889..5115a876fe 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -45,7 +45,7 @@ struct ShadowSampleOffsets { }; ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) { - float shadowScale = getShadowScale(); + float shadowScale = getShadowScale(); ShadowSampleOffsets offsets; #if SHADOW_SCREEN_SPACE_DITHER @@ -67,10 +67,10 @@ ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) { ivec2 offset = coords & ivec2(1,1); offset.y = (offset.x+offset.y) & 1; - offsets.points[0] = shadowScale * vec3(offset + PCFkernel[0], 0.0); - offsets.points[1] = shadowScale * vec3(offset + PCFkernel[1], 0.0); - offsets.points[2] = shadowScale * vec3(offset + PCFkernel[2], 0.0); - offsets.points[3] = shadowScale * vec3(offset + PCFkernel[3], 0.0); + offsets.points[0] = shadowScale * vec3(vec2(offset) + PCFkernel[0], 0.0); + offsets.points[1] = shadowScale * vec3(vec2(offset) + PCFkernel[1], 0.0); + offsets.points[2] = shadowScale * vec3(vec2(offset) + PCFkernel[2], 0.0); + offsets.points[3] = shadowScale * vec3(vec2(offset) + PCFkernel[3], 0.0); return offsets; } @@ -104,7 +104,7 @@ float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDe vec3 cascadeMix; bvec4 isPixelOnCascade; int cascadeIndex; - float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0, 1); + float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0.0, 1.0); for (cascadeIndex=0 ; cascadeIndex layout(std140, binding=RENDER_UTILS_BUFFER_SHADOW_PARAMS) uniform shadowTransformBuffer { - ShadowParameters shadow; + ShadowParameters shadow; }; int getShadowCascadeCount() { @@ -30,26 +30,26 @@ float evalShadowFalloff(float depth) { } mat4 getShadowReprojection(int cascadeIndex) { - return shadow.cascades[cascadeIndex].reprojection; + return shadow.cascades[cascadeIndex].reprojection; } float getShadowScale() { - return shadow.invMapSize; + return shadow.invMapSize; } float getShadowFixedBias(int cascadeIndex) { - return shadow.cascades[cascadeIndex].fixedBias; + return shadow.cascades[cascadeIndex].fixedBias; } float getShadowSlopeBias(int cascadeIndex) { - return shadow.cascades[cascadeIndex].slopeBias; + return shadow.cascades[cascadeIndex].slopeBias; } // Compute the texture coordinates from world coordinates vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) { - vec4 shadowCoord = getShadowReprojection(cascadeIndex) * position; - return vec4(shadowCoord.xyz, 1.0); + vec4 shadowCoord = getShadowReprojection(cascadeIndex) * position; + return vec4(shadowCoord.xyz, 1.0); } bool isShadowCascadeProjectedOnPixel(vec4 cascadeTexCoords) { diff --git a/libraries/render-utils/src/Shadows_shared.slh b/libraries/render-utils/src/Shadows_shared.slh index bc8063e018..9cb46587a2 100644 --- a/libraries/render-utils/src/Shadows_shared.slh +++ b/libraries/render-utils/src/Shadows_shared.slh @@ -8,8 +8,8 @@ #define SHADOW_CASCADE_MAX_COUNT 4 struct ShadowTransform { - MAT4 reprojection; - float fixedBias; + MAT4 reprojection; + float fixedBias; float slopeBias; float _padding1; float _padding2; diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index 079e6f75ef..b9a6581f1d 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -77,8 +77,9 @@ void SoftAttachmentModel::updateClusterMatrices() { } // post the blender if we're not currently waiting for one to finish - if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + auto modelBlender = DependencyManager::get(); + if (_blendedVertexBuffersInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; - DependencyManager::get()->noteRequiresBlend(getThisPointer()); + modelBlender->noteRequiresBlend(getThisPointer()); } } diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index dd9167d248..8ef0dc0d73 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -70,9 +70,8 @@ void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& st const glm::vec2& bounds, bool layered) { // The font does all the OpenGL work if (_font) { - // Cache color so that the pointer stays valid. _color = color; - _font->drawString(batch, x, y, str, &_color, _effectType, bounds, layered); + _font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds, layered); } } diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h index 175802ef6e..6c91411e1d 100644 --- a/libraries/render-utils/src/TextRenderer3D.h +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -15,12 +15,14 @@ #include #include #include +#include namespace gpu { class Batch; } class Font; +#include "text/Font.h" #include "text/EffectType.h" #include "text/FontFamilies.h" @@ -51,7 +53,7 @@ private: // text color glm::vec4 _color; - + Font::DrawInfo _drawInfo; std::shared_ptr _font; }; diff --git a/libraries/render-utils/src/debug_deferred_buffer.slf b/libraries/render-utils/src/debug_deferred_buffer.slf index aee04cba90..013640d910 100644 --- a/libraries/render-utils/src/debug_deferred_buffer.slf +++ b/libraries/render-utils/src/debug_deferred_buffer.slf @@ -16,14 +16,8 @@ <@include gpu/Color.slh@> <$declareColorWheel()$> -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_DEPTH) uniform sampler2D linearDepthMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_HALF_DEPTH) uniform sampler2D halfLinearDepthMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_HALF_NORMAL) uniform sampler2D halfNormalMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION) uniform sampler2D occlusionMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION_BLURRED) uniform sampler2D occlusionBlurredMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_SCATTERING) uniform sampler2D scatteringMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_VELOCITY) uniform sampler2D velocityMap; -layout(binding=RENDER_UTILS_TEXTURE_DEBUG_SHADOWS) uniform sampler2DArrayShadow shadowMaps; +layout(binding=RENDER_UTILS_DEBUG_TEXTURE0) uniform sampler2D debugTexture0; +layout(binding=RENDER_UTILS_TEXTURE_SHADOW) uniform sampler2DArrayShadow shadowMaps; <@include ShadowCore.slh@> @@ -31,7 +25,7 @@ layout(binding=RENDER_UTILS_TEXTURE_DEBUG_SHADOWS) uniform sampler2DArrayShadow <@include debug_deferred_buffer_shared.slh@> -layout(std140) uniform parametersBuffer { +layout(std140, binding=RENDER_UTILS_BUFFER_DEBUG_DEFERRED_PARAMS) uniform parametersBuffer { DebugParameters parameters; }; diff --git a/libraries/render-utils/src/debug_deferred_buffer_shared.slh b/libraries/render-utils/src/debug_deferred_buffer_shared.slh index 2d11a66d61..f5d6797c50 100644 --- a/libraries/render-utils/src/debug_deferred_buffer_shared.slh +++ b/libraries/render-utils/src/debug_deferred_buffer_shared.slh @@ -7,7 +7,7 @@ struct DebugParameters { - INT32 _shadowCascadeIndex; + INT32 _shadowCascadeIndex; }; // <@if 1@> diff --git a/libraries/render-utils/src/deferred_light_limited.slv b/libraries/render-utils/src/deferred_light_limited.slv index fb59b8e78f..1c835aacf7 100644 --- a/libraries/render-utils/src/deferred_light_limited.slv +++ b/libraries/render-utils/src/deferred_light_limited.slv @@ -18,7 +18,8 @@ <$declareStandardTransform()$> -uniform vec4 sphereParam; +// FIXME make into a uniform buffer or push constant if this shader ever comes into use +vec4 sphereParam = vec4(0.0); layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; @@ -41,7 +42,7 @@ void main(void) { } #endif #endif - _texCoord01.xy = vec4(projected.xy, 0.0, 1.0) * gl_Position.w; + _texCoord01 = vec4(projected.xy, 0.0, 1.0) * gl_Position.w; } else { const float depth = -1.0; //Draw at near plane const vec4 UNIT_QUAD[4] = vec4[4]( @@ -60,7 +61,7 @@ void main(void) { #endif #endif - _texCoord01.xy = vec4((pos.xy + 1.0) * 0.5, 0.0, 1.0); + _texCoord01 = vec4((pos.xy + 1.0) * 0.5, 0.0, 1.0); #ifdef GPU_TRANSFORM_IS_STEREO #ifdef GPU_TRANSFORM_STEREO_SPLIT_SCREEN diff --git a/libraries/render-utils/src/drawWorkloadProxy.slf b/libraries/render-utils/src/drawWorkloadProxy.slf index 32dceab00a..f0bd9d474c 100644 --- a/libraries/render-utils/src/drawWorkloadProxy.slf +++ b/libraries/render-utils/src/drawWorkloadProxy.slf @@ -13,9 +13,9 @@ <@include DeferredBufferWrite.slh@> <@include gpu/Paint.slh@> -in vec4 varColor; -in vec3 varTexcoord; -in vec3 varEyePos; +layout(location=0) in vec4 varColor; +layout(location=1) in vec3 varTexcoord; +layout(location=2) in vec3 varEyePos; void main(void) { if (varColor.w > 0.0) { diff --git a/libraries/render-utils/src/drawWorkloadProxy.slv b/libraries/render-utils/src/drawWorkloadProxy.slv index 28a62070f9..7a01702107 100644 --- a/libraries/render-utils/src/drawWorkloadProxy.slv +++ b/libraries/render-utils/src/drawWorkloadProxy.slv @@ -19,9 +19,9 @@ <$declareWorkloadProxies()$> -out vec4 varColor; -out vec3 varTexcoord; -out vec3 varEyePos; +layout(location=0) out vec4 varColor; +layout(location=1) out vec3 varTexcoord; +layout(location=2) out vec3 varEyePos; void main(void) { const vec4 UNIT_SPRITE[3] = vec4[3]( diff --git a/libraries/render-utils/src/drawWorkloadView.slf b/libraries/render-utils/src/drawWorkloadView.slf index c44dae4a24..b638824204 100644 --- a/libraries/render-utils/src/drawWorkloadView.slf +++ b/libraries/render-utils/src/drawWorkloadView.slf @@ -14,9 +14,9 @@ <@include DeferredBufferWrite.slh@> <@include gpu/Paint.slh@> -in vec4 varColor; -in vec3 varTexcoord; -in vec3 varEyePos; +layout(location=0) in vec4 varColor; +layout(location=1) in vec3 varTexcoord; +layout(location=2) in vec3 varEyePos; void main(void) { if (varColor.w > 0.0) { diff --git a/libraries/render-utils/src/drawWorkloadView.slv b/libraries/render-utils/src/drawWorkloadView.slv index 291a8c86cd..db4a33c450 100644 --- a/libraries/render-utils/src/drawWorkloadView.slv +++ b/libraries/render-utils/src/drawWorkloadView.slv @@ -18,9 +18,9 @@ <@include WorkloadResource.slh@> <$declareWorkloadViews()$> -out vec4 varColor; -out vec3 varTexcoord; -out vec3 varEyePos; +layout(location=0) out vec4 varColor; +layout(location=1) out vec3 varTexcoord; +layout(location=2) out vec3 varEyePos; const int NUM_VERTICES_PER_SEGMENT = 2; const int NUM_SEGMENT_PER_VIEW_REGION = 65; @@ -79,7 +79,7 @@ void main(void) { <$transformModelToEyeDir(cam, obj, originSpaceTan, tanEye)$> lateralDir = normalize(cross(vec3(0.0, 0.0, 1.0), normalize(tanEye))); - posEye.xyz += (0.005 * abs(posEye.z) * (regionID + 1)) * (-1.0 + 2.0 * float(segmentVertexID)) * lateralDir; + posEye.xyz += (0.005 * abs(posEye.z) * float(regionID + 1)) * (-1.0 + 2.0 * float(segmentVertexID)) * lateralDir; varEyePos = posEye.xyz; <$transformEyeToClipPos(cam, posEye, gl_Position)$> diff --git a/libraries/render-utils/src/fxaa.slf b/libraries/render-utils/src/fxaa.slf index 2dddcc795b..f1096a3054 100644 --- a/libraries/render-utils/src/fxaa.slf +++ b/libraries/render-utils/src/fxaa.slf @@ -24,7 +24,9 @@ precision mediump int; layout(binding=0) uniform sampler2D colorTexture; //uniform sampler2D historyTexture; -layout(location=0) uniform vec2 texcoordOffset; + +// FIXME make into a uniform buffer or push constant if this shader ever comes into use +vec2 texcoordOffset = vec2(0.0); layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; @@ -66,7 +68,7 @@ void main() { outFragColor.w = 1.0; }*/ - if (gl_FragCoord.x > 800) { + if (gl_FragCoord.x > 800.0) { /* // filter width limit for dependent "two-tap" texture samples float FXAA_SPAN_MAX = 8.0; diff --git a/libraries/render-utils/src/fxaa_blend.slf b/libraries/render-utils/src/fxaa_blend.slf index aca050f047..c051801659 100644 --- a/libraries/render-utils/src/fxaa_blend.slf +++ b/libraries/render-utils/src/fxaa_blend.slf @@ -18,7 +18,14 @@ layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; layout(binding=0) uniform sampler2D colorTexture; -layout(location=GPU_UNIFORM_EXTRA0) uniform float sharpenIntensity; + +struct FxaaBlendParams { + vec4 sharpenIntensity; +}; + +layout(binding=0) uniform fxaaBlendParamsBuffer { + FxaaBlendParams params; +}; void main(void) { vec4 pixels[9]; @@ -37,7 +44,7 @@ void main(void) { sharpenedPixel = pixels[4]*6.8 - (pixels[1]+pixels[3]+pixels[5]+pixels[7]) - (pixels[0]+pixels[2]+pixels[6]+pixels[8])*0.7; - vec4 minColor = max(vec4(0), pixels[4]-vec4(0.5)); - vec4 maxColor = pixels[4]+vec4(0.5); - outFragColor = clamp(pixels[4] + sharpenedPixel * sharpenIntensity, minColor, maxColor); + vec4 minColor = max(vec4(0), pixels[4]-vec4(0.5)); + vec4 maxColor = pixels[4]+vec4(0.5); + outFragColor = clamp(pixels[4] + sharpenedPixel * params.sharpenIntensity.x, minColor, maxColor); } diff --git a/libraries/render-utils/src/glowLine.slf b/libraries/render-utils/src/glowLine.slf index 4e80b3358a..c65d8d6488 100644 --- a/libraries/render-utils/src/glowLine.slf +++ b/libraries/render-utils/src/glowLine.slf @@ -22,9 +22,9 @@ void main(void) { float alpha = 1.0 - abs(distanceFromCenter); // Convert from a linear alpha curve to a sharp peaked one - alpha = _color.a * pow(alpha, 10.0); - - // Drop everything where the curve falls off to nearly nothing + alpha = _color.a * pow(alpha, 10.0); + + // Drop everything where the curve falls off to nearly nothing if (alpha <= 0.05) { discard; } diff --git a/libraries/render-utils/src/lightClusters_drawClusterContent.slf b/libraries/render-utils/src/lightClusters_drawClusterContent.slf index 65ae8f423e..27cb838566 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterContent.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterContent.slf @@ -42,7 +42,7 @@ void main(void) { ivec3 cluster = clusterGrid_getCluster(clusterIndex); int numLights = cluster.x + cluster.y; - float numLightsScale = clamp(numLights * 0.05, 0.01, 1.0); + float numLightsScale = clamp(float(numLights) * 0.05, 0.01, 1.0); int clusterOffset = cluster.z; @@ -90,6 +90,6 @@ void main(void) { numLightTouching++; } - _fragColor = vec4(colorRamp(1.0 - (numLightTouching / 12.0f)), (numLightTouching > 0 ? 0.5 + 0.5 * numLightsScale : 0.0)); + _fragColor = vec4(colorRamp(1.0 - (float(numLightTouching) / 12.0f)), (numLightTouching > 0 ? 0.5 + 0.5 * numLightsScale : 0.0)); } diff --git a/libraries/render-utils/src/lightClusters_drawClusterContent.slv b/libraries/render-utils/src/lightClusters_drawClusterContent.slv index d7e4a66a6a..1303cf3926 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterContent.slv +++ b/libraries/render-utils/src/lightClusters_drawClusterContent.slv @@ -56,7 +56,7 @@ void main(void) { ivec3 cluster = clusterGrid_getCluster(gpu_InstanceID()); int numLights = cluster.x + cluster.y; - float numLightsScale = clamp(numLights * 0.1, 0.0, 1.0); + float numLightsScale = clamp(float(numLights) * 0.1, 0.0, 1.0); ivec3 clusterPos = frustumGrid_indexToCluster(gpu_InstanceID()); diff --git a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf index 4efb60a259..abbd86dd70 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf @@ -39,7 +39,7 @@ void main(void) { ivec3 cluster = clusterGrid_getCluster(frustumGrid_clusterToIndex(clusterPos)); int numLights = cluster.x + cluster.y; - float numLightsScale = clamp(numLights * 0.05, 0.01, 1.0); + float numLightsScale = clamp(float(numLights) * 0.05, 0.01, 1.0); ivec3 dims = frustumGrid.dims.xyz; diff --git a/libraries/render-utils/src/model_translucent_normal_map.slf b/libraries/render-utils/src/model_translucent_normal_map.slf index 45eee9d160..7ac6982cfa 100644 --- a/libraries/render-utils/src/model_translucent_normal_map.slf +++ b/libraries/render-utils/src/model_translucent_normal_map.slf @@ -87,7 +87,7 @@ void main(void) { 1.0, occlusionTex, fragPositionES, - fragPositionWS, + fragPositionWS, albedo, fresnel, metallic, diff --git a/libraries/render-utils/src/model_translucent_normal_map_fade.slf b/libraries/render-utils/src/model_translucent_normal_map_fade.slf index 2ede2bfbaa..2c182aeb19 100644 --- a/libraries/render-utils/src/model_translucent_normal_map_fade.slf +++ b/libraries/render-utils/src/model_translucent_normal_map_fade.slf @@ -97,7 +97,7 @@ void main(void) { 1.0, occlusionTex, fragPositionES, - fragPositionWS, + fragPositionWS, albedo, fresnel, metallic, diff --git a/libraries/render-utils/src/parabola_translucent.slf b/libraries/render-utils/src/parabola_translucent.slf new file mode 100644 index 0000000000..62b5b15193 --- /dev/null +++ b/libraries/render-utils/src/parabola_translucent.slf @@ -0,0 +1,18 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gondelman on 9/10/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 DeferredBufferWrite.slh@> + +layout(location=0) in vec4 _color; + +void main(void) { + packDeferredFragmentTranslucent(vec3(1.0, 0.0, 0.0), _color.a, _color.rgb, DEFAULT_FRESNEL, DEFAULT_ROUGHNESS); +} diff --git a/libraries/render-utils/src/render-utils/ShaderConstants.h b/libraries/render-utils/src/render-utils/ShaderConstants.h index afb195c240..ccf6314a39 100644 --- a/libraries/render-utils/src/render-utils/ShaderConstants.h +++ b/libraries/render-utils/src/render-utils/ShaderConstants.h @@ -43,9 +43,6 @@ #define RENDER_UTILS_BUFFER_AMBIENT_LIGHT 6 #define RENDER_UTILS_BUFFER_LIGHT_INDEX 7 -#define RENDER_UTILS_UNIFORM_LIGHT_RADIUS 0 -#define RENDER_UTILS_UNIFORM_LIGHT_TEXCOORD_TRANSFORM 1 - // Deferred lighting resolution #define RENDER_UTILS_TEXTURE_DEFERRRED_COLOR 0 #define RENDER_UTILS_TEXTURE_DEFERRRED_NORMAL 1 @@ -126,14 +123,8 @@ // Debugging #define RENDER_UTILS_BUFFER_DEBUG_SKYBOX 5 -#define RENDER_UTILS_TEXTURE_DEBUG_DEPTH 11 -#define RENDER_UTILS_TEXTURE_DEBUG_HALF_DEPTH 12 -#define RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION 13 -#define RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION_BLURRED 14 -#define RENDER_UTILS_TEXTURE_DEBUG_VELOCITY 15 -#define RENDER_UTILS_TEXTURE_DEBUG_SHADOWS 16 -#define RENDER_UTILS_TEXTURE_DEBUG_HALF_NORMAL 17 -#define RENDER_UTILS_TEXTURE_DEBUG_SCATTERING 18 +#define RENDER_UTILS_DEBUG_TEXTURE0 11 +#define RENDER_UTILS_BUFFER_DEBUG_DEFERRED_PARAMS 1 // @@ -146,8 +137,6 @@ enum Uniform { TextOutline = RENDER_UTILS_UNIFORM_TEXT_OUTLINE, TaaSharpenIntensity = GPU_UNIFORM_EXTRA0, HighlightOutlineWidth = GPU_UNIFORM_EXTRA0, - LightRadius = RENDER_UTILS_UNIFORM_LIGHT_RADIUS, - TexcoordTransform = RENDER_UTILS_UNIFORM_LIGHT_TEXCOORD_TRANSFORM, }; } @@ -174,6 +163,7 @@ enum Buffer { BloomParams = RENDER_UTILS_BUFFER_BLOOM_PARAMS, ToneMappingParams = RENDER_UTILS_BUFFER_TM_PARAMS, ShadowParams = RENDER_UTILS_BUFFER_SHADOW_PARAMS, + DebugDeferredParams = RENDER_UTILS_BUFFER_DEBUG_DEFERRED_PARAMS, }; } // namespace buffer @@ -212,14 +202,7 @@ enum Texture { BloomColor = RENDER_UTILS_TEXTURE_BLOOM_COLOR, ToneMappingColor = RENDER_UTILS_TEXTURE_TM_COLOR, TextFont = RENDER_UTILS_TEXTURE_TEXT_FONT, - DebugDepth = RENDER_UTILS_TEXTURE_DEBUG_DEPTH, - DebugHalfDepth = RENDER_UTILS_TEXTURE_DEBUG_HALF_DEPTH, - DebugOcclusion = RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION, - DebugOcclusionBlurred = RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION_BLURRED, - DebugVelocity = RENDER_UTILS_TEXTURE_DEBUG_VELOCITY, - DebugShadows = RENDER_UTILS_TEXTURE_DEBUG_SHADOWS, - DebugHalfNormal = RENDER_UTILS_TEXTURE_DEBUG_HALF_NORMAL, - DebugScattering = RENDER_UTILS_TEXTURE_DEBUG_SCATTERING, + DebugTexture0 = RENDER_UTILS_DEBUG_TEXTURE0, }; } // namespace texture diff --git a/libraries/render-utils/src/render-utils/fxaa.slp b/libraries/render-utils/src/render-utils/fxaa.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render-utils/src/render-utils/fxaa.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render-utils/src/render-utils/parabola_translucent.slp b/libraries/render-utils/src/render-utils/parabola_translucent.slp new file mode 100644 index 0000000000..ab3f1d4126 --- /dev/null +++ b/libraries/render-utils/src/render-utils/parabola_translucent.slp @@ -0,0 +1 @@ +VERTEX parabola diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index 2fbaa03900..d35396e469 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -14,8 +14,15 @@ <@include render-utils/ShaderConstants.h@> layout(binding=0) uniform sampler2D Font; -layout(location=RENDER_UTILS_UNIFORM_TEXT_OUTLINE) uniform bool Outline; -layout(location=RENDER_UTILS_UNIFORM_TEXT_COLOR) uniform vec4 Color; + +struct TextParams { + vec4 color; + vec4 outline; +}; + +layout(binding=0) uniform textParamsBuffer { + TextParams params; +}; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; @@ -32,7 +39,7 @@ const float taaBias = pow(2.0, TAA_TEXTURE_LOD_BIAS); float evalSDF(vec2 texCoord) { // retrieve signed distance float sdf = textureLod(Font, texCoord, TAA_TEXTURE_LOD_BIAS).g; - if (Outline) { + if (params.outline.x > 0.0) { if (sdf > interiorCutoff) { sdf = 1.0 - sdf; } else { @@ -47,12 +54,12 @@ void main() { vec2 dxTexCoord = dFdx(_texCoord0) * 0.5 * taaBias; vec2 dyTexCoord = dFdy(_texCoord0) * 0.5 * taaBias; - // Perform 4x supersampling for anisotropic filtering + // Perform 4x supersampling for anisotropic filtering float a; - a = evalSDF(_texCoord0); - a += evalSDF(_texCoord0+dxTexCoord); - a += evalSDF(_texCoord0+dyTexCoord); - a += evalSDF(_texCoord0+dxTexCoord+dyTexCoord); + a = evalSDF(_texCoord0); + a += evalSDF(_texCoord0+dxTexCoord); + a += evalSDF(_texCoord0+dyTexCoord); + a += evalSDF(_texCoord0+dxTexCoord+dyTexCoord); a *= 0.25; // discard if invisible @@ -62,8 +69,8 @@ void main() { packDeferredFragment( normalize(_normalWS), - a * Color.a, - Color.rgb, + a * params.color.a, + params.color.rgb, DEFAULT_ROUGHNESS, DEFAULT_METALLIC, DEFAULT_EMISSIVE, diff --git a/libraries/render-utils/src/sdf_text3D_transparent.slf b/libraries/render-utils/src/sdf_text3D_transparent.slf index 218236c26b..9dffca2038 100644 --- a/libraries/render-utils/src/sdf_text3D_transparent.slf +++ b/libraries/render-utils/src/sdf_text3D_transparent.slf @@ -14,8 +14,15 @@ <@include render-utils/ShaderConstants.h@> layout(binding=0) uniform sampler2D Font; -layout(location=RENDER_UTILS_UNIFORM_TEXT_OUTLINE) uniform bool Outline; -layout(location=RENDER_UTILS_UNIFORM_TEXT_COLOR) uniform vec4 Color; + +struct TextParams { + vec4 color; + vec4 outline; +}; + +layout(binding=0) uniform textParamsBuffer { + TextParams params; +}; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; @@ -31,7 +38,7 @@ const float outlineExpansion = 0.2; void main() { // retrieve signed distance float sdf = texture(Font, _texCoord0).g; - if (Outline) { + if (params.outline.x > 0.0) { if (sdf > interiorCutoff) { sdf = 1.0 - sdf; } else { @@ -51,8 +58,8 @@ void main() { packDeferredFragmentTranslucent( normalize(_normalWS), - a * Color.a, - Color.rgb, + a * params.color.a, + params.color.rgb, DEFAULT_FRESNEL, DEFAULT_ROUGHNESS); } \ No newline at end of file diff --git a/libraries/render-utils/src/simple_fade.slf b/libraries/render-utils/src/simple_fade.slf index e9f94c29bc..97ed0c570c 100644 --- a/libraries/render-utils/src/simple_fade.slf +++ b/libraries/render-utils/src/simple_fade.slf @@ -91,7 +91,7 @@ void main(void) { normal, 1.0, diffuse+fadeEmissive, - max(0, 1.0 - shininess / 128.0), + max(0.0, 1.0 - shininess / 128.0), DEFAULT_METALLIC, specular, specular); @@ -100,7 +100,7 @@ void main(void) { normal, 1.0, diffuse, - max(0, 1.0 - shininess / 128.0), + max(0.0, 1.0 - shininess / 128.0), length(specular), DEFAULT_EMISSIVE+fadeEmissive, DEFAULT_OCCLUSION, diff --git a/libraries/render-utils/src/simple_transparent_textured.slf b/libraries/render-utils/src/simple_transparent_textured.slf index 0e6198de68..5573a7aa22 100644 --- a/libraries/render-utils/src/simple_transparent_textured.slf +++ b/libraries/render-utils/src/simple_transparent_textured.slf @@ -17,7 +17,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(location=0) uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; diff --git a/libraries/render-utils/src/ssao.slh b/libraries/render-utils/src/ssao.slh index 5341a1682a..b149d8f912 100644 --- a/libraries/render-utils/src/ssao.slh +++ b/libraries/render-utils/src/ssao.slh @@ -137,14 +137,14 @@ float getBlurCoef(int c) { float getAngleDitheringWorldPos(in vec3 pixelWorldPos) { vec3 worldPosFract = fract(pixelWorldPos * 1.0); - ivec3 pixelPos = ivec3(worldPosFract * 256); + ivec3 pixelPos = ivec3(worldPosFract * 256.0); - return isDitheringEnabled() * ((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) + (3 * pixelPos.y ^ pixelPos.z + pixelPos.x * pixelPos.z)) * 10 + getFrameDithering(); + return isDitheringEnabled() * float(((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) + (3 * pixelPos.y ^ pixelPos.z + pixelPos.x * pixelPos.z)) * 10) + getFrameDithering(); } float getAngleDithering(in ivec2 pixelPos) { // Hash function used in the AlchemyAO paper - return isDitheringEnabled() * (3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10 + getFrameDithering(); + return isDitheringEnabled() * float((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10) + getFrameDithering(); } float evalDiskRadius(float Zeye, vec2 imageSize) { @@ -162,7 +162,7 @@ const float TWO_PI = 6.28; vec3 getUnitTapLocation(int sampleNumber, float spinAngle){ // Radius relative to ssR - float alpha = float(sampleNumber + 0.5) * getInvNumSamples(); + float alpha = (float(sampleNumber) + 0.5) * getInvNumSamples(); float angle = alpha * (getNumSpiralTurns() * TWO_PI) + spinAngle; return vec3(cos(angle), sin(angle), alpha); } @@ -219,7 +219,7 @@ vec3 getTapLocationClamped(int sampleNumber, float spinAngle, float outerRadius, if (redoTap) { tap.xy = tapPos - pixelPos; tap.z = length(tap.xy); - tap.z = 0; + tap.z = 0.0; } return tap; @@ -253,7 +253,7 @@ vec3 fetchTapUnfiltered(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) { vec2 tapUV = (vec2(ssP) + vec2(0.5)) / imageSize; - vec2 fetchUV = vec2(tapUV.x + side.w * 0.5 * (side.x - tapUV.x), tapUV.y); + vec2 fetchUV = vec2(tapUV.x + float(side.w) * 0.5 * (float(side.x) - tapUV.x), tapUV.y); vec3 P; P.xy = tapUV; @@ -263,7 +263,7 @@ vec3 fetchTapUnfiltered(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) { } vec3 fetchTap(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) { - int mipLevel = evalMipFromRadius(tap.z * doFetchMips()); + int mipLevel = evalMipFromRadius(tap.z * float(doFetchMips())); ivec2 ssP = ivec2(tap.xy) + ssC; ivec2 ssPFull = ivec2(ssP.x + side.y, ssP.y); @@ -276,7 +276,7 @@ vec3 fetchTap(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) { ivec2 mipP = clamp(ssPFull >> mipLevel, ivec2(0), mipSize - ivec2(1)); vec2 tapUV = (vec2(ssP) + vec2(0.5)) / imageSize; - vec2 fetchUV = vec2(tapUV.x + side.w * 0.5 * (side.x - tapUV.x), tapUV.y); + vec2 fetchUV = vec2(tapUV.x + float(side.w) * 0.5 * (float(side.x) - tapUV.x), tapUV.y); // vec2 tapUV = (vec2(mipP) + vec2(0.5)) / vec2(mipSize); vec3 P; diff --git a/libraries/render-utils/src/ssao_debugOcclusion.slf b/libraries/render-utils/src/ssao_debugOcclusion.slf index 007fd0cd7b..ab7989e35e 100644 --- a/libraries/render-utils/src/ssao_debugOcclusion.slf +++ b/libraries/render-utils/src/ssao_debugOcclusion.slf @@ -84,15 +84,15 @@ void main(void) { float keepTapRadius = 1.0; int keepedMip = -1; bool keep = false; - - for (int i = 0; i < getNumSamples(); ++i) { + int sampleCount = int(getNumSamples()); + for (int i = 0; i < sampleCount; ++i) { vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, cursorPixelPos, imageSize); // The occluding point in camera space vec2 fragToTap = vec2(ssC) + tap.xy - fragCoord.xy; if (dot(fragToTap,fragToTap) < keepTapRadius) { keep = true; - keepedMip = evalMipFromRadius(tap.z * doFetchMips()); + keepedMip = evalMipFromRadius(tap.z * float(doFetchMips())); } vec3 tapUVZ = fetchTap(side, ssC, tap, imageSize); diff --git a/libraries/render-utils/src/ssao_makeOcclusion.slf b/libraries/render-utils/src/ssao_makeOcclusion.slf index 1b638d4270..3934b9eddc 100644 --- a/libraries/render-utils/src/ssao_makeOcclusion.slf +++ b/libraries/render-utils/src/ssao_makeOcclusion.slf @@ -52,8 +52,9 @@ void main(void) { // Accumulate the Obscurance for each samples float sum = 0.0; - for (int i = 0; i < getNumSamples(); ++i) { - vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, ssC, imageSize); + int sampleCount = int(getNumSamples()); + for (int i = 0; i < sampleCount; ++i) { + vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, vec2(ssC), imageSize); vec3 tapUVZ = fetchTap(side, ssC, tap, imageSize); @@ -68,10 +69,10 @@ void main(void) { // Bilateral box-filter over a quad for free, respecting depth edges // (the difference that this makes is subtle) if (abs(dFdx(Cp.z)) < 0.02) { - A -= dFdx(A) * ((ssC.x & 1) - 0.5); + A -= dFdx(A) * (float(ssC.x & 1) - 0.5); } if (abs(dFdy(Cp.z)) < 0.02) { - A -= dFdy(A) * ((ssC.y & 1) - 0.5); + A -= dFdy(A) * (float(ssC.y & 1) - 0.5); } diff --git a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf index 00b60d6fd4..8664fa16fd 100644 --- a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf +++ b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf @@ -26,7 +26,8 @@ layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 _fragColor; -layout(location=GPU_UNIFORM_EXTRA0) uniform vec2 uniformCursorTexcoord = vec2(0.5); +// FIXME make into a uniform buffer or push constant if this shader ever comes into use +vec2 uniformCursorTexcoord = vec2(0.5); //uniform vec3 uniformLightVector = vec3(1.0); @@ -79,7 +80,7 @@ vec3 drawScatteringTableUV(vec2 cursor, vec2 texcoord) { vec3 distance = vec3(0.0); for (int c = 0; c < 3; c++) { - vec2 BRDFuv = vec2(clamp(bentNdotL[c] * 0.5 + 0.5, 0.0, 1.0), clamp(2 * curvature, 0.0, 1.0)); + vec2 BRDFuv = vec2(clamp(bentNdotL[c] * 0.5 + 0.5, 0.0, 1.0), clamp(2.0 * curvature, 0.0, 1.0)); vec2 delta = BRDFuv - texcoord; distance[c] = 1.0 - dot(delta, delta); } diff --git a/libraries/render-utils/src/surfaceGeometry_copyDepth.slf b/libraries/render-utils/src/surfaceGeometry_copyDepth.slf index 7eb097224e..f018ee1105 100644 --- a/libraries/render-utils/src/surfaceGeometry_copyDepth.slf +++ b/libraries/render-utils/src/surfaceGeometry_copyDepth.slf @@ -16,7 +16,7 @@ layout(binding=0) uniform sampler2D depthMap; layout(location=0) out vec4 outFragColor; void main(void) { - float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; - outFragColor = vec4(Zdb, 0.0, 0.0, 1.0); + float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; + outFragColor = vec4(Zdb, 0.0, 0.0, 1.0); } diff --git a/libraries/render-utils/src/taa.slh b/libraries/render-utils/src/taa.slh index 26ffe55263..2161ad9524 100644 --- a/libraries/render-utils/src/taa.slh +++ b/libraries/render-utils/src/taa.slh @@ -24,10 +24,10 @@ layout(binding=RENDER_UTILS_TEXTURE_TAA_NEXT) uniform sampler2D nextMap; struct TAAParams { - float none; - float blend; - float covarianceGamma; - float debugShowVelocityThreshold; + float none; + float blend; + float covarianceGamma; + float debugShowVelocityThreshold; ivec4 flags; vec4 pixelInfo_orbZoom; vec4 regionInfo; @@ -77,47 +77,47 @@ vec2 taa_getRegionFXAA() { #define USE_YCOCG 1 vec4 taa_fetchColor(sampler2D map, vec2 uv) { - vec4 c = texture(map, uv); - // Apply rapid pseudo tonemapping as TAA is applied to a tonemapped image, using luminance as weight, as proposed in - // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf - float lum = dot(vec3(0.3,0.5,0.2),c.rgb); - c.rgb = c.rgb / (1.0+lum); + vec4 c = texture(map, uv); + // Apply rapid pseudo tonemapping as TAA is applied to a tonemapped image, using luminance as weight, as proposed in + // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf + float lum = dot(vec3(0.3,0.5,0.2),c.rgb); + c.rgb = c.rgb / (1.0+lum); #if USE_YCOCG - return vec4(color_LinearToYCoCg(c.rgb), c.a); + return vec4(color_LinearToYCoCg(c.rgb), c.a); #else - return c; + return c; #endif } vec3 taa_resolveColor(vec3 color) { #if USE_YCOCG - color = max(vec3(0), color_YCoCgToUnclampedLinear(color)); + color = max(vec3(0), color_YCoCgToUnclampedLinear(color)); #endif - // Apply rapid inverse tonemapping, using luminance as weight, as proposed in - // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf - float lum = dot(vec3(0.3,0.5,0.2),color.rgb); - color = color / (1.0-lum); - return color; + // Apply rapid inverse tonemapping, using luminance as weight, as proposed in + // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf + float lum = dot(vec3(0.3,0.5,0.2),color.rgb); + color = color / (1.0-lum); + return color; } vec4 taa_fetchSourceMap(vec2 uv) { - return taa_fetchColor(sourceMap, uv); + return taa_fetchColor(sourceMap, uv); } vec4 taa_fetchHistoryMap(vec2 uv) { - return taa_fetchColor(historyMap, uv); + return taa_fetchColor(historyMap, uv); } vec4 taa_fetchNextMap(vec2 uv) { - return taa_fetchColor(nextMap, uv); + return taa_fetchColor(nextMap, uv); } vec2 taa_fetchVelocityMap(vec2 uv) { - return texture(velocityMap, uv).xy; + return texture(velocityMap, uv).xy; } float taa_fetchDepth(vec2 uv) { - return -texture(depthMap, vec2(uv), 0).x; + return -texture(depthMap, vec2(uv), 0.0).x; } @@ -141,35 +141,35 @@ vec2 taa_getTexelSize() { vec3 taa_findClosestFragment3x3(vec2 uv) { - vec2 dd = abs(taa_getTexelSize()); - vec2 du = vec2(dd.x, 0.0); - vec2 dv = vec2(0.0, dd.y); + vec2 dd = abs(taa_getTexelSize()); + vec2 du = vec2(dd.x, 0.0); + vec2 dv = vec2(0.0, dd.y); - vec3 dtl = vec3(-1, -1, taa_fetchDepth(uv - dv - du)); - vec3 dtc = vec3( 0, -1, taa_fetchDepth(uv - dv)); - vec3 dtr = vec3( 1, -1, taa_fetchDepth(uv - dv + du)); + vec3 dtl = vec3(-1, -1, taa_fetchDepth(uv - dv - du)); + vec3 dtc = vec3( 0, -1, taa_fetchDepth(uv - dv)); + vec3 dtr = vec3( 1, -1, taa_fetchDepth(uv - dv + du)); - vec3 dml = vec3(-1, 0, taa_fetchDepth(uv - du)); - vec3 dmc = vec3( 0, 0, taa_fetchDepth(uv)); - vec3 dmr = vec3( 1, 0, taa_fetchDepth(uv + du)); + vec3 dml = vec3(-1, 0, taa_fetchDepth(uv - du)); + vec3 dmc = vec3( 0, 0, taa_fetchDepth(uv)); + vec3 dmr = vec3( 1, 0, taa_fetchDepth(uv + du)); - vec3 dbl = vec3(-1, 1, taa_fetchDepth(uv + dv - du)); - vec3 dbc = vec3( 0, 1, taa_fetchDepth(uv + dv)); - vec3 dbr = vec3( 1, 1, taa_fetchDepth(uv + dv + du)); + vec3 dbl = vec3(-1, 1, taa_fetchDepth(uv + dv - du)); + vec3 dbc = vec3( 0, 1, taa_fetchDepth(uv + dv)); + vec3 dbr = vec3( 1, 1, taa_fetchDepth(uv + dv + du)); - vec3 dmin = dtl; - if (ZCMP_GT(dmin.z, dtc.z)) dmin = dtc; - if (ZCMP_GT(dmin.z, dtr.z)) dmin = dtr; + vec3 dmin = dtl; + if (ZCMP_GT(dmin.z, dtc.z)) dmin = dtc; + if (ZCMP_GT(dmin.z, dtr.z)) dmin = dtr; - if (ZCMP_GT(dmin.z, dml.z)) dmin = dml; - if (ZCMP_GT(dmin.z, dmc.z)) dmin = dmc; - if (ZCMP_GT(dmin.z, dmr.z)) dmin = dmr; + if (ZCMP_GT(dmin.z, dml.z)) dmin = dml; + if (ZCMP_GT(dmin.z, dmc.z)) dmin = dmc; + if (ZCMP_GT(dmin.z, dmr.z)) dmin = dmr; - if (ZCMP_GT(dmin.z, dbl.z)) dmin = dbl; - if (ZCMP_GT(dmin.z, dbc.z)) dmin = dbc; - if (ZCMP_GT(dmin.z, dbr.z)) dmin = dbr; + if (ZCMP_GT(dmin.z, dbl.z)) dmin = dbl; + if (ZCMP_GT(dmin.z, dbc.z)) dmin = dbc; + if (ZCMP_GT(dmin.z, dbr.z)) dmin = dbr; - return vec3(uv + dd.xy * dmin.xy, dmin.z); + return vec3(uv + dd.xy * dmin.xy, dmin.z); } vec2 taa_fetchVelocityMapBest(vec2 uv) { @@ -230,7 +230,7 @@ vec2 taa_fromEyeUVToFragUV(vec2 eyeUV, int stereoSide) { vec2 fragUV = eyeUV; if (isStereo()) { fragUV.x *= 0.5; - fragUV.x += stereoSide*0.5; + fragUV.x += float(stereoSide)*0.5; } return fragUV; } @@ -264,8 +264,8 @@ mat3 taa_evalNeighbourColorVariance(vec3 sourceColor, vec2 fragUV, vec2 fragVelo vec2 texelSize = taa_getTexelSize(); - vec2 du = vec2(texelSize.x, 0.0); - vec2 dv = vec2(0.0, texelSize.y); + vec2 du = vec2(texelSize.x, 0.0); + vec2 dv = vec2(0.0, texelSize.y); vec3 sampleColor = taa_fetchSourceMap(fragUV - dv - du).rgb; vec3 sumSamples = sampleColor; @@ -320,72 +320,72 @@ mat3 taa_evalNeighbourColorRegion(vec3 sourceColor, vec2 fragUV, vec2 fragVeloci vec3 cmin, cmax, cavg; #if MINMAX_3X3_ROUNDED - vec2 du = vec2(texelSize.x, 0.0); - vec2 dv = vec2(0.0, texelSize.y); + vec2 du = vec2(texelSize.x, 0.0); + vec2 dv = vec2(0.0, texelSize.y); - vec3 ctl = taa_fetchSourceMap(fragUV - dv - du).rgb; - vec3 ctc = taa_fetchSourceMap(fragUV - dv).rgb; - vec3 ctr = taa_fetchSourceMap(fragUV - dv + du).rgb; - vec3 cml = taa_fetchSourceMap(fragUV - du).rgb; - vec3 cmc = sourceColor; //taa_fetchSourceMap(fragUV).rgb; // could resuse the same osurce sample isn't it ? - vec3 cmr = taa_fetchSourceMap(fragUV + du).rgb; - vec3 cbl = taa_fetchSourceMap(fragUV + dv - du).rgb; - vec3 cbc = taa_fetchSourceMap(fragUV + dv).rgb; - vec3 cbr = taa_fetchSourceMap(fragUV + dv + du).rgb; + vec3 ctl = taa_fetchSourceMap(fragUV - dv - du).rgb; + vec3 ctc = taa_fetchSourceMap(fragUV - dv).rgb; + vec3 ctr = taa_fetchSourceMap(fragUV - dv + du).rgb; + vec3 cml = taa_fetchSourceMap(fragUV - du).rgb; + vec3 cmc = sourceColor; //taa_fetchSourceMap(fragUV).rgb; // could resuse the same osurce sample isn't it ? + vec3 cmr = taa_fetchSourceMap(fragUV + du).rgb; + vec3 cbl = taa_fetchSourceMap(fragUV + dv - du).rgb; + vec3 cbc = taa_fetchSourceMap(fragUV + dv).rgb; + vec3 cbr = taa_fetchSourceMap(fragUV + dv + du).rgb; - cmin = min(ctl, min(ctc, min(ctr, min(cml, min(cmc, min(cmr, min(cbl, min(cbc, cbr)))))))); - cmax = max(ctl, max(ctc, max(ctr, max(cml, max(cmc, max(cmr, max(cbl, max(cbc, cbr)))))))); + cmin = min(ctl, min(ctc, min(ctr, min(cml, min(cmc, min(cmr, min(cbl, min(cbc, cbr)))))))); + cmax = max(ctl, max(ctc, max(ctr, max(cml, max(cmc, max(cmr, max(cbl, max(cbc, cbr)))))))); - #if MINMAX_3X3_ROUNDED || USE_YCOCG || USE_CLIPPING - cavg = (ctl + ctc + ctr + cml + cmc + cmr + cbl + cbc + cbr) / 9.0; + #if MINMAX_3X3_ROUNDED || USE_YCOCG || USE_CLIPPING + cavg = (ctl + ctc + ctr + cml + cmc + cmr + cbl + cbc + cbr) / 9.0; #elif cavg = (cmin + cmax ) * 0.5; - #endif + #endif - #if MINMAX_3X3_ROUNDED - vec3 cmin5 = min(ctc, min(cml, min(cmc, min(cmr, cbc)))); - vec3 cmax5 = max(ctc, max(cml, max(cmc, max(cmr, cbc)))); - vec3 cavg5 = (ctc + cml + cmc + cmr + cbc) / 5.0; - cmin = 0.5 * (cmin + cmin5); - cmax = 0.5 * (cmax + cmax5); - cavg = 0.5 * (cavg + cavg5); - #endif + #if MINMAX_3X3_ROUNDED + vec3 cmin5 = min(ctc, min(cml, min(cmc, min(cmr, cbc)))); + vec3 cmax5 = max(ctc, max(cml, max(cmc, max(cmr, cbc)))); + vec3 cavg5 = (ctc + cml + cmc + cmr + cbc) / 5.0; + cmin = 0.5 * (cmin + cmin5); + cmax = 0.5 * (cmax + cmax5); + cavg = 0.5 * (cavg + cavg5); + #endif #else - const float _SubpixelThreshold = 0.5; - const float _GatherBase = 0.5; - const float _GatherSubpixelMotion = 0.1666; + const float _SubpixelThreshold = 0.5; + const float _GatherBase = 0.5; + const float _GatherSubpixelMotion = 0.1666; - vec2 texel_vel = fragVelocity * imageSize; - float texel_vel_mag = length(texel_vel) * -fragZe; - float k_subpixel_motion = clamp(_SubpixelThreshold / (0.0001 + texel_vel_mag), 0.0, 1.0); - float k_min_max_support = _GatherBase + _GatherSubpixelMotion * k_subpixel_motion; + vec2 texel_vel = fragVelocity * imageSize; + float texel_vel_mag = length(texel_vel) * -fragZe; + float k_subpixel_motion = clamp(_SubpixelThreshold / (0.0001 + texel_vel_mag), 0.0, 1.0); + float k_min_max_support = _GatherBase + _GatherSubpixelMotion * k_subpixel_motion; - vec2 ss_offset01 = k_min_max_support * vec2(-texelSize.x, texelSize.y); - vec2 ss_offset11 = k_min_max_support * vec2(texelSize.x, texelSize.y); - vec3 c00 = taa_fetchSourceMap(fragUV - ss_offset11).rgb; - vec3 c10 = taa_fetchSourceMap(fragUV - ss_offset01).rgb; - vec3 c01 = taa_fetchSourceMap(fragUV + ss_offset01).rgb; - vec3 c11 = taa_fetchSourceMap(fragUV + ss_offset11).rgb; + vec2 ss_offset01 = k_min_max_support * vec2(-texelSize.x, texelSize.y); + vec2 ss_offset11 = k_min_max_support * vec2(texelSize.x, texelSize.y); + vec3 c00 = taa_fetchSourceMap(fragUV - ss_offset11).rgb; + vec3 c10 = taa_fetchSourceMap(fragUV - ss_offset01).rgb; + vec3 c01 = taa_fetchSourceMap(fragUV + ss_offset01).rgb; + vec3 c11 = taa_fetchSourceMap(fragUV + ss_offset11).rgb; - cmin = min(c00, min(c10, min(c01, c11))); - cmax = max(c00, max(c10, max(c01, c11))); + cmin = min(c00, min(c10, min(c01, c11))); + cmax = max(c00, max(c10, max(c01, c11))); cavg = (cmin + cmax ) * 0.5; - #if USE_YCOCG || USE_CLIPPING - cavg = (c00 + c10 + c01 + c11) / 4.0; + #if USE_YCOCG || USE_CLIPPING + cavg = (c00 + c10 + c01 + c11) / 4.0; #elif cavg = (cmin + cmax ) * 0.5; - #endif + #endif #endif - // shrink chroma min-max - #if USE_YCOCG - vec2 chroma_extent = vec2(0.25 * 0.5 * (cmax.r - cmin.r)); - vec2 chroma_center = sourceColor.gb; - cmin.yz = chroma_center - chroma_extent; - cmax.yz = chroma_center + chroma_extent; - cavg.yz = chroma_center; - #endif + // shrink chroma min-max + #if USE_YCOCG + vec2 chroma_extent = vec2(0.25 * 0.5 * (cmax.r - cmin.r)); + vec2 chroma_center = sourceColor.gb; + cmin.yz = chroma_center - chroma_extent; + cmax.yz = chroma_center + chroma_extent; + cavg.yz = chroma_center; + #endif return mat3(cmin, cmax, cavg); } @@ -393,22 +393,22 @@ mat3 taa_evalNeighbourColorRegion(vec3 sourceColor, vec2 fragUV, vec2 fragVeloci //#define USE_OPTIMIZATIONS 0 vec3 taa_clampColor(vec3 colorMin, vec3 colorMax, vec3 colorSource, vec3 color) { - const float eps = 0.00001; + const float eps = 0.00001; vec3 p = colorSource; vec3 q = color; - // note: only clips towards aabb center (but fast!) - vec3 p_clip = 0.5 * (colorMax + colorMin); - vec3 e_clip = 0.5 * (colorMax - colorMin) + vec3(eps); + // note: only clips towards aabb center (but fast!) + vec3 p_clip = 0.5 * (colorMax + colorMin); + vec3 e_clip = 0.5 * (colorMax - colorMin) + vec3(eps); - vec3 v_clip = q - p_clip; - vec3 v_unit = v_clip.xyz / e_clip; - vec3 a_unit = abs(v_unit); - float ma_unit = max(a_unit.x, max(a_unit.y, a_unit.z)); + vec3 v_clip = q - p_clip; + vec3 v_unit = v_clip.xyz / e_clip; + vec3 a_unit = abs(v_unit); + float ma_unit = max(a_unit.x, max(a_unit.y, a_unit.z)); - if (ma_unit > 1.0) - return p_clip + v_clip / ma_unit; - else - return q;// point inside aabb + if (ma_unit > 1.0) + return p_clip + v_clip / ma_unit; + else + return q;// point inside aabb } vec3 taa_evalConstrainColor(vec3 sourceColor, vec2 sourceUV, vec2 sourceVel, vec3 candidateColor) { @@ -416,25 +416,25 @@ vec3 taa_evalConstrainColor(vec3 sourceColor, vec2 sourceUV, vec2 sourceVel, vec colorMinMaxAvg = taa_evalNeighbourColorVariance(sourceColor, sourceUV, sourceVel); - // clamp history to neighbourhood of current sample + // clamp history to neighbourhood of current sample return taa_clampColor(colorMinMaxAvg[0], colorMinMaxAvg[1], sourceColor, candidateColor); } vec3 taa_evalFeedbackColor(vec3 sourceColor, vec3 historyColor, float blendFactor) { const float _FeedbackMin = 0.1; const float _FeedbackMax = 0.9; - // feedback weight from unbiased luminance diff (t.lottes) - #if USE_YCOCG - float lum0 = sourceColor.r; - float lum1 = historyColor.r; - #else - float lum0 = Luminance(sourceColor.rgb); - float lum1 = Luminance(historyColor.rgb); - #endif - float unbiased_diff = abs(lum0 - lum1) / max(lum0, max(lum1, 0.2)); - float unbiased_weight = 1.0 - unbiased_diff; - float unbiased_weight_sqr = unbiased_weight * unbiased_weight; - float k_feedback = mix(_FeedbackMin, _FeedbackMax, unbiased_weight_sqr); + // feedback weight from unbiased luminance diff (t.lottes) + #if USE_YCOCG + float lum0 = sourceColor.r; + float lum1 = historyColor.r; + #else + float lum0 = Luminance(sourceColor.rgb); + float lum1 = Luminance(historyColor.rgb); + #endif + float unbiased_diff = abs(lum0 - lum1) / max(lum0, max(lum1, 0.2)); + float unbiased_weight = 1.0 - unbiased_diff; + float unbiased_weight_sqr = unbiased_weight * unbiased_weight; + float k_feedback = mix(_FeedbackMin, _FeedbackMax, unbiased_weight_sqr); vec3 nextColor = mix(historyColor, sourceColor, k_feedback * blendFactor).xyz; @@ -478,7 +478,7 @@ vec3 taa_evalFXAA(vec2 fragUV) { vec3 rgbSW = texture(sourceMap, fragUV + (vec2(-1.0, +1.0) * texelSize)).xyz; vec3 rgbSE = texture(sourceMap, fragUV + (vec2(+1.0, +1.0) * texelSize)).xyz; vec3 rgbM = texture(sourceMap, fragUV).xyz; - + // convert RGB values to luminance vec3 luma = vec3(0.299, 0.587, 0.114); float lumaNW = dot(rgbNW, luma); @@ -486,11 +486,11 @@ vec3 taa_evalFXAA(vec2 fragUV) { float lumaSW = dot(rgbSW, luma); float lumaSE = dot(rgbSE, luma); float lumaM = dot( rgbM, luma); - + // luma range of local neighborhood float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); - + // direction perpendicular to local luma gradient vec2 dir; dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); @@ -502,7 +502,7 @@ vec3 taa_evalFXAA(vec2 fragUV) { float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * texelSize; - + // perform additional texture sampling perpendicular to gradient vec3 rgbA = (1.0 / 2.0) * ( texture(sourceMap, fragUV + dir * (1.0 / 3.0 - 0.5)).xyz + diff --git a/libraries/render-utils/src/taa_blend.slf b/libraries/render-utils/src/taa_blend.slf index d2e23b590f..50575a6a07 100644 --- a/libraries/render-utils/src/taa_blend.slf +++ b/libraries/render-utils/src/taa_blend.slf @@ -23,7 +23,7 @@ void main(void) { // Pixel being shaded - vec3 sourceColor = texture(sourceMap, varTexCoord0).xyz; + vec3 sourceColor = texture(sourceMap, varTexCoord0).xyz; vec2 imageSize = getWidthHeight(0); vec2 texelSize = getInvWidthHeight(); @@ -57,7 +57,7 @@ void main(void) { vec2 cursorVelocityDir = cursorVelocity / cursorVelocityLength; vec2 cursorVelocityNor = vec2(cursorVelocityDir.y, -cursorVelocityDir.x); - if ((dot(cursorVelocityDir, cursorToFragVec) < 0) && abs(dot(cursorVelocityNor, cursorToFragVec)) < 1.0) { + if ((dot(cursorVelocityDir, cursorToFragVec) < 0.0) && abs(dot(cursorVelocityNor, cursorToFragVec)) < 1.0) { vec3 speedColor = taa_getVelocityColorRelative(cursorToFragLength); @@ -69,7 +69,7 @@ void main(void) { float tenPercentHeight = 0.1 * imageSize.y; float centerWidth = imageSize.x * 0.5; - //vec2 nextOrbPos = vec2(centerWidth, imageSize.y - 3 * tenPercentHeight); + //vec2 nextOrbPos = vec2(centerWidth, imageSize.y - 3.0 * tenPercentHeight); vec2 nextOrbPos = cursorPos; vec2 nextOrbPosToPix = pixPos - nextOrbPos; float nextOrbPosToPixLength = length(nextOrbPosToPix); @@ -124,9 +124,9 @@ void main(void) { // draw region splitter if ((abs(distToRegionDebug) < getInvWidthHeight().x) || (abs(distToRegionFXAA) < getInvWidthHeight().x)) { - outFragColor.rgb = vec3(1.0, 1.0, 0.0); - return; - } + outFragColor.rgb = vec3(1.0, 1.0, 0.0); + return; + } if (distToRegionFXAA > 0.0) { return; @@ -138,7 +138,7 @@ void main(void) { return; } - outFragColor = vec4(nextColor, 1.0); + outFragColor = vec4(nextColor, 1.0); vec3 prevColor = nextColor; @@ -146,7 +146,7 @@ void main(void) { prevColor = texture(historyMap, prevTexCoord).xyz; } - outFragColor.xyz = mix(prevColor, vec3(1,0,1), clamp(distance(prevColor, nextColor) - 0.01, 0, 1)); + outFragColor.xyz = mix(prevColor, vec3(1,0,1), clamp(distance(prevColor, nextColor) - 0.01, 0.0, 1.0)); if (pixVelocityLength > params.debugShowVelocityThreshold) { vec3 speedColor = taa_getVelocityColorAboveThreshold(pixVelocityLength); diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index aca6ddb79f..68a1e59dc3 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -256,29 +256,30 @@ void Font::setupGPU() { } } -void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2& bounds) { - _verticesBuffer = std::make_shared(); - _numVertices = 0; - _indicesBuffer = std::make_shared(); - _numIndices = 0; +void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds) { + drawInfo.verticesBuffer = std::make_shared(); + drawInfo.indicesBuffer = std::make_shared(); + drawInfo.indexCount = 0; + int numVertices = 0; - _lastStringRendered = str; - _lastBounds = bounds; + drawInfo.string = str; + drawInfo.bounds = bounds; + drawInfo.origin = origin; // Top left of text - glm::vec2 advance = glm::vec2(x, y); + glm::vec2 advance = origin; foreach(const QString& token, tokenizeForWrapping(str)) { bool isNewLine = (token == QString('\n')); bool forceNewLine = false; // Handle wrapping - if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > x + bounds.x)) { + if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > origin.x + bounds.x)) { // We are out of the x bound, force new line forceNewLine = true; } if (isNewLine || forceNewLine) { // Character return, move the advance to a new line - advance = glm::vec2(x, advance.y - _leading); + advance = glm::vec2(origin.x, advance.y - _leading); if (isNewLine) { // No need to draw anything, go directly to next token @@ -288,7 +289,7 @@ void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2 break; } } - if ((bounds.y != -1) && (advance.y - _fontSize < -y - bounds.y)) { + if ((bounds.y != -1) && (advance.y - _fontSize < -origin.y - bounds.y)) { // We are out of the y bound, stop drawing break; } @@ -297,11 +298,11 @@ void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2 if (!isNewLine) { for (auto c : token) { auto glyph = _glyphs[c]; - quint16 verticesOffset = _numVertices; + quint16 verticesOffset = numVertices; QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _ascent)); - _verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd); - _numVertices += 4; + drawInfo.verticesBuffer->append(qd); + numVertices += 4; // Sam's recommended triangle slices // Triangle tri1 = { v0, v1, v3 }; @@ -327,8 +328,8 @@ void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2 indices[3] = verticesOffset + 2; indices[4] = verticesOffset + 1; indices[5] = verticesOffset + 3; - _indicesBuffer->append(sizeof(indices), (const gpu::Byte*)indices); - _numIndices += NUMBER_OF_INDICES_PER_QUAD; + drawInfo.indicesBuffer->append(sizeof(indices), (const gpu::Byte*)indices); + drawInfo.indexCount += NUMBER_OF_INDICES_PER_QUAD; // Advance by glyph size @@ -341,26 +342,39 @@ void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2 } } -void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4* color, - EffectType effectType, const glm::vec2& bounds, bool layered) { +void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString& str, const glm::vec4& color, + EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds, bool layered) { if (str == "") { return; } - if (str != _lastStringRendered || bounds != _lastBounds) { - rebuildVertices(x, y, str, bounds); + if (str != drawInfo.string || bounds != drawInfo.bounds || origin != drawInfo.origin) { + buildVertices(drawInfo, str, origin, bounds); } setupGPU(); - batch.setPipeline(((*color).a < 1.0f || layered) ? _transparentPipeline : _pipeline); - batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture); - batch._glUniform1i(render_utils::slot::uniform::TextOutline, (effectType == OUTLINE_EFFECT)); + struct GpuDrawParams { + glm::vec4 color; + glm::vec4 outline; + }; + + if (!drawInfo.paramsBuffer || drawInfo.params.color != color || drawInfo.params.effect != effectType) { + drawInfo.params.color = color; + drawInfo.params.effect = effectType; + GpuDrawParams gpuDrawParams; + gpuDrawParams.color = ColorUtils::sRGBToLinearVec4(drawInfo.params.color); + gpuDrawParams.outline.x = (drawInfo.params.effect == OUTLINE_EFFECT) ? 1 : 0; + drawInfo.paramsBuffer = std::make_shared(sizeof(GpuDrawParams), nullptr); + drawInfo.paramsBuffer->setSubData(0, sizeof(GpuDrawParams), (const gpu::Byte*)&gpuDrawParams); + } // need the gamma corrected color here - glm::vec4 lrgba = ColorUtils::sRGBToLinearVec4(*color); - batch._glUniform4fv(render_utils::slot::uniform::TextColor, 1, (const float*)&lrgba); + + batch.setPipeline((color.a < 1.0f || layered) ? _transparentPipeline : _pipeline); batch.setInputFormat(_format); - batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); - batch.setIndexBuffer(gpu::UINT16, _indicesBuffer, 0); - batch.drawIndexed(gpu::TRIANGLES, _numIndices, 0); + batch.setInputBuffer(0, drawInfo.verticesBuffer, 0, _format->getChannels().at(0)._stride); + batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture); + batch.setUniformBuffer(0, drawInfo.paramsBuffer, 0, sizeof(GpuDrawParams)); + batch.setIndexBuffer(gpu::UINT16, drawInfo.indicesBuffer, 0); + batch.drawIndexed(gpu::TRIANGLES, drawInfo.indexCount, 0); } diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index 2fa2b65fa5..9a0a9b4734 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -23,16 +23,34 @@ public: void read(QIODevice& path); + struct DrawParams { + vec4 color{ -1 }; + EffectType effect; + }; + + struct DrawInfo { + gpu::BufferPointer verticesBuffer; + gpu::BufferPointer indicesBuffer; + gpu::BufferPointer paramsBuffer; + uint32_t indexCount; + + QString string; + glm::vec2 origin; + glm::vec2 bounds; + DrawParams params; + }; + glm::vec2 computeExtent(const QString& str) const; float getFontSize() const { return _fontSize; } // Render string to batch - void drawString(gpu::Batch& batch, float x, float y, const QString& str, - const glm::vec4* color, EffectType effectType, - const glm::vec2& bound, bool layered = false); + void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str, + const glm::vec4& color, EffectType effectType, + const glm::vec2& origin, const glm::vec2& bound, bool layered = false); static Pointer load(const QString& family); + private: static Pointer load(QIODevice& fontFile); QStringList tokenizeForWrapping(const QString& str) const; @@ -40,7 +58,7 @@ private: glm::vec2 computeTokenExtent(const QString& str) const; const Glyph& getGlyph(const QChar& c) const; - void rebuildVertices(float x, float y, const QString& str, const glm::vec2& bounds); + void buildVertices(DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds); void setupGPU(); @@ -66,15 +84,7 @@ private: gpu::PipelinePointer _transparentPipeline; gpu::TexturePointer _texture; gpu::Stream::FormatPointer _format; - gpu::BufferPointer _verticesBuffer; - gpu::BufferPointer _indicesBuffer; gpu::BufferStreamPointer _stream; - unsigned int _numVertices = 0; - unsigned int _numIndices = 0; - - // last string render characteristics - QString _lastStringRendered; - glm::vec2 _lastBounds; }; #endif diff --git a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf index 6932766d63..083440dbf8 100644 --- a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf +++ b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf @@ -27,11 +27,11 @@ void main(void) { ivec4 stereoSide; ivec2 framePixelPos = getPixelPosTexcoordPosAndSide(gl_FragCoord.xy, pixelPos, texcoordPos, stereoSide); - float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; + float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; - // The position of the pixel fragment in Eye space then in world space + // The position of the pixel fragment in Eye space then in world space vec3 eyePos = evalUnjitteredEyePositionFromZdb(stereoSide.x, Zdb, texcoordPos); - vec3 worldPos = (getViewInverse() * vec4(eyePos, 1.0)).xyz; + vec3 worldPos = (getViewInverse() * vec4(eyePos, 1.0)).xyz; vec3 prevEyePos = (getPreviousView() * vec4(worldPos, 1.0)).xyz; vec4 prevClipPos = (getUnjitteredProjection(stereoSide.x) * vec4(prevEyePos, 1.0)); diff --git a/libraries/render-utils/src/zone_drawAmbient.slf b/libraries/render-utils/src/zone_drawAmbient.slf index e560d91308..f20d83e913 100644 --- a/libraries/render-utils/src/zone_drawAmbient.slf +++ b/libraries/render-utils/src/zone_drawAmbient.slf @@ -44,7 +44,7 @@ void main(void) { // vec3 ambient = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(lightAmbient), fragNormal).xyz; // _fragColor = vec4( 0.5 * (fragNormal + vec3(1.0)), 1.0); - vec3 color = (sphereUV.x > 0 ? ambientMap : ambientSH); + vec3 color = (sphereUV.x > 0.0 ? ambientMap : ambientSH); color = color * 1.0 - base.w + base.xyz * base.w; const float INV_GAMMA_22 = 1.0 / 2.2; diff --git a/libraries/render-utils/src/zone_drawKeyLight.slf b/libraries/render-utils/src/zone_drawKeyLight.slf index 99e9357cb0..7174914ed8 100644 --- a/libraries/render-utils/src/zone_drawKeyLight.slf +++ b/libraries/render-utils/src/zone_drawKeyLight.slf @@ -36,7 +36,7 @@ void main(void) { vec3 outSpherePos = normalize(vec3(sphereUV, -sqrt(1.0 - sphereR2))); vec3 outNormal = vec3(getViewInverse() * vec4(outSpherePos, 0.0)); - float val = step(SUN_THRESHOLD, dot(-lightDirection, outNormal)); + float val = step(SUN_THRESHOLD, dot(-lightDirection, outNormal)); color = lightIrradiance * vec3(val); @@ -45,7 +45,7 @@ void main(void) { vec3 inSpherePos = normalize(vec3(inSphereUV, sqrt(1.0 - dot(inSphereUV.xy, inSphereUV.xy)))); vec3 inNormal = vec3(getViewInverse() * vec4(inSpherePos, 0.0)); - vec3 marbleColor = max(lightIrradiance * vec3(dot(-lightDirection, inNormal)), vec3(0.01)); + vec3 marbleColor = max(lightIrradiance * vec3(dot(-lightDirection, inNormal)), vec3(0.01)); color += marbleColor; } diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index 627d4f17b2..dd061c0092 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -73,6 +73,7 @@ namespace render { Args(const gpu::ContextPointer& context, float sizeScale = 1.0f, int boundaryLevelAdjust = 0, + float lodAngleHalfTan = 0.1f, RenderMode renderMode = DEFAULT_RENDER_MODE, DisplayMode displayMode = MONO, DebugFlags debugFlags = RENDER_DEBUG_NONE, @@ -80,6 +81,8 @@ namespace render { _context(context), _sizeScale(sizeScale), _boundaryLevelAdjust(boundaryLevelAdjust), + _lodAngleHalfTan(lodAngleHalfTan), + _lodAngleHalfTanSq(lodAngleHalfTan * lodAngleHalfTan), _renderMode(renderMode), _displayMode(displayMode), _debugFlags(debugFlags), @@ -105,8 +108,12 @@ namespace render { std::stack _viewFrustums; glm::ivec4 _viewport { 0.0f, 0.0f, 1.0f, 1.0f }; glm::vec3 _boomOffset { 0.0f, 0.0f, 1.0f }; + float _sizeScale { 1.0f }; int _boundaryLevelAdjust { 0 }; + float _lodAngleHalfTan{ 0.1f }; + float _lodAngleHalfTanSq{ _lodAngleHalfTan * _lodAngleHalfTan }; + RenderMode _renderMode { DEFAULT_RENDER_MODE }; DisplayMode _displayMode { MONO }; DebugFlags _debugFlags { RENDER_DEBUG_NONE }; diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index b5f1718f6c..b2930032a3 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -155,7 +155,7 @@ void FetchSpatialTree::run(const RenderContextPointer& renderContext, const Inpu // Octree selection! float threshold = 0.0f; if (queryFrustum.isPerspective()) { - threshold = getPerspectiveAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust); + threshold = args->_lodAngleHalfTan; if (frustumResolution.y > 0) { threshold = glm::max(queryFrustum.getFieldOfView() / frustumResolution.y, threshold); } @@ -445,7 +445,7 @@ void ApplyCullFunctorOnItemBounds::run(const RenderContextPointer& renderContext } } -void FetchSpatialSelection::run(const RenderContextPointer& renderContext, +void FilterSpatialSelection::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems) { assert(renderContext->args); auto& scene = renderContext->_scene; diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index 2ef9e92eaa..99ca7abe6c 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -147,12 +147,12 @@ namespace render { }; - class FetchSpatialSelection { + class FilterSpatialSelection { public: using Inputs = render::VaryingSet2; - using JobModel = Job::ModelIO; + using JobModel = Job::ModelIO; - FetchSpatialSelection() {} + FilterSpatialSelection() {} void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems); }; diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 2cb8a5d8ef..6c87d2f56b 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -36,6 +36,9 @@ const gpu::PipelinePointer DrawSceneOctree::getDrawCellBoundsPipeline() { // Good to go add the brand new pipeline _drawCellBoundsPipeline = gpu::Pipeline::create(program, state); + _cellBoundsFormat = std::make_shared(); + _cellBoundsFormat->setAttribute(0, 0, gpu::Element(gpu::VEC4, gpu::INT32, gpu::XYZW), 0, gpu::Stream::PER_INSTANCE); + _cellBoundsBuffer = std::make_shared(); } return _drawCellBoundsPipeline; } @@ -82,8 +85,11 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS // bind the one gpu::Pipeline we need batch.setPipeline(getDrawCellBoundsPipeline()); + batch.setInputFormat(_cellBoundsFormat); - auto drawCellBounds = [this, &scene, &batch](const std::vector& cells) { + std::vector cellBounds; + auto drawCellBounds = [this, &cellBounds, &scene, &batch](const std::vector& cells) { + cellBounds.reserve(cellBounds.size() + cells.size()); for (const auto& cellID : cells) { auto cell = scene->getSpatialTree().getConcreteCell(cellID); auto cellLoc = cell.getlocation(); @@ -98,14 +104,19 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS } else if (!empty && !_showVisibleCells) { continue; } - - batch._glUniform4iv(gpu::slot::uniform::Extra0, 1, ((const int*)(&cellLocation))); - batch.draw(gpu::LINES, 24, 0); + cellBounds.push_back(cellLocation); } }; drawCellBounds(inSelection.cellSelection.insideCells); drawCellBounds(inSelection.cellSelection.partialCells); + auto size = cellBounds.size() * sizeof(ivec4); + if (size > _cellBoundsBuffer->getSize()) { + _cellBoundsBuffer->resize(size); + } + _cellBoundsBuffer->setSubData(0, cellBounds); + batch.setInputBuffer(0, _cellBoundsBuffer, 0, sizeof(ivec4)); + batch.drawInstanced((uint32_t)cellBounds.size(), gpu::LINES, 24); // Draw the LOD Reticle { diff --git a/libraries/render/src/render/DrawSceneOctree.h b/libraries/render/src/render/DrawSceneOctree.h index 0b2cd6f685..3f7c07ded3 100644 --- a/libraries/render/src/render/DrawSceneOctree.h +++ b/libraries/render/src/render/DrawSceneOctree.h @@ -54,6 +54,8 @@ namespace render { gpu::PipelinePointer _drawCellBoundsPipeline; gpu::PipelinePointer _drawLODReticlePipeline; gpu::PipelinePointer _drawItemBoundPipeline; + gpu::BufferPointer _cellBoundsBuffer; + gpu::Stream::FormatPointer _cellBoundsFormat; bool _showVisibleCells; // initialized by Config bool _showEmptyCells; // initialized by Config diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 9b7d4ace2b..a1b61a4e77 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -170,13 +170,45 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp batch.setPipeline(getDrawItemStatusPipeline()); if (_showNetwork) { - for (size_t i = 0; i < itemBounds.size(); i++) { - batch._glUniform3fv(gpu::slot::uniform::Extra0, 1, (const float*)&itemBounds[i].bound.getCorner()); - batch._glUniform3fv(gpu::slot::uniform::Extra1, 1, ((const float*)&itemBounds[i].bound.getScale())); - batch._glUniform4iv(gpu::slot::uniform::Extra2, 1, (const int*)&(itemStatus[i].first)); - batch._glUniform4iv(gpu::slot::uniform::Extra3, 1, (const int*)&(itemStatus[i].second)); - batch.draw(gpu::TRIANGLES, 24 * NUM_STATUS_VEC4_PER_ITEM, 0); + if (!_instanceBuffer) { + _instanceBuffer = std::make_shared(); } + + struct InstanceData { + vec4 boundPos; + vec4 boundDim; + ivec4 status0; + ivec4 status1; + }; + + if (!_vertexFormat) { + _vertexFormat = std::make_shared(); + _vertexFormat->setAttribute(0, 0, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), offsetof(InstanceData, boundPos), gpu::Stream::PER_INSTANCE); + _vertexFormat->setAttribute(1, 0, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), offsetof(InstanceData, boundDim), gpu::Stream::PER_INSTANCE); + _vertexFormat->setAttribute(2, 0, gpu::Element(gpu::VEC4, gpu::INT32, gpu::XYZW), offsetof(InstanceData, status0), gpu::Stream::PER_INSTANCE); + _vertexFormat->setAttribute(3, 0, gpu::Element(gpu::VEC4, gpu::INT32, gpu::XYZW), offsetof(InstanceData, status1), gpu::Stream::PER_INSTANCE); + } + + batch.setInputFormat(_vertexFormat); + std::vector instanceData; + instanceData.resize(itemBounds.size()); + for (size_t i = 0; i < itemBounds.size(); i++) { + InstanceData& item = instanceData[i]; + const auto& bound = itemBounds[i].bound; + const auto& status = itemStatus[i]; + item.boundPos = vec4(bound.getCorner(), 1.0f); + item.boundDim = vec4(bound.getScale(), 1.0f); + item.status0 = status.first; + item.status1 = status.second; + } + + auto instanceBufferSize = sizeof(InstanceData) * instanceData.size(); + if (_instanceBuffer->getSize() < instanceBufferSize) { + _instanceBuffer->resize(instanceBufferSize); + } + _instanceBuffer->setSubData(0, instanceData); + batch.setInputBuffer(0, _instanceBuffer, 0, sizeof(InstanceData)); + batch.drawInstanced((uint32_t)instanceData.size(), gpu::TRIANGLES, 24 * NUM_STATUS_VEC4_PER_ITEM); } batch.setResourceTexture(0, 0); }); diff --git a/libraries/render/src/render/DrawStatus.h b/libraries/render/src/render/DrawStatus.h index 96269fda4d..2959ca59c5 100644 --- a/libraries/render/src/render/DrawStatus.h +++ b/libraries/render/src/render/DrawStatus.h @@ -63,6 +63,8 @@ namespace render { gpu::PipelinePointer _drawItemStatusPipeline; gpu::BufferPointer _boundsBuffer; + gpu::BufferPointer _instanceBuffer; + gpu::Stream::FormatPointer _vertexFormat; gpu::TexturePointer _statusIconMap; }; } diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 8fb800291e..12f75049b2 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -184,8 +184,13 @@ void DrawBounds::run(const RenderContextPointer& renderContext, _drawBuffer = std::make_shared(sizeOfItemBound); } - _drawBuffer->setData(numItems * sizeOfItemBound, (const gpu::Byte*) items.data()); + if (!_paramsBuffer) { + _paramsBuffer = std::make_shared(sizeof(vec4), nullptr); + } + _drawBuffer->setData(numItems * sizeOfItemBound, (const gpu::Byte*) items.data()); + glm::vec4 color(glm::vec3(0.0f), -(float) numItems); + _paramsBuffer->setSubData(0, color); gpu::doInBatch("DrawBounds::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; @@ -202,7 +207,7 @@ void DrawBounds::run(const RenderContextPointer& renderContext, batch.setPipeline(getPipeline()); glm::vec4 color(glm::vec3(0.0f), -(float) numItems); - batch._glUniform4fv(gpu::slot::uniform::Color, 1, (const float*)(&color)); + batch.setUniformBuffer(0, _paramsBuffer); batch.setResourceBuffer(0, _drawBuffer); static const int NUM_VERTICES_PER_CUBE = 24; @@ -212,9 +217,10 @@ void DrawBounds::run(const RenderContextPointer& renderContext, gpu::Stream::FormatPointer DrawQuadVolume::_format; -DrawQuadVolume::DrawQuadVolume(const glm::vec3& color) : - _color{ color } { +DrawQuadVolume::DrawQuadVolume(const glm::vec3& color) { _meshVertices = gpu::BufferView(std::make_shared(sizeof(glm::vec3) * 8, nullptr), gpu::Element::VEC3F_XYZ); + _params = std::make_shared(sizeof(glm::vec4), nullptr); + _params->setSubData(0, vec4(color, 1.0)); if (!_format) { _format = std::make_shared(); _format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); @@ -248,8 +254,7 @@ void DrawQuadVolume::run(const render::RenderContextPointer& renderContext, cons batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); batch.setPipeline(getPipeline()); - - batch._glUniform4f(0, _color.x, _color.y, _color.z, 1.0f); + batch.setUniformBuffer(0, _params); batch.setInputFormat(_format); batch.setInputBuffer(gpu::Stream::POSITION, _meshVertices); batch.setIndexBuffer(indices); diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h index 6b98fb2977..1ef4b8caf1 100755 --- a/libraries/render/src/render/DrawTask.h +++ b/libraries/render/src/render/DrawTask.h @@ -66,6 +66,7 @@ private: const gpu::PipelinePointer getPipeline(); gpu::PipelinePointer _boundsPipeline; gpu::BufferPointer _drawBuffer; + gpu::BufferPointer _paramsBuffer; }; class DrawQuadVolumeConfig : public render::JobConfig { @@ -95,7 +96,7 @@ protected: const gpu::BufferView& indices, int indexCount); gpu::BufferView _meshVertices; - glm::vec3 _color; + gpu::BufferPointer _params; bool _isUpdateEnabled{ true }; static gpu::Stream::FormatPointer _format; diff --git a/libraries/render/src/render/drawCellBounds.slv b/libraries/render/src/render/drawCellBounds.slv index f3cc4c6411..24cc6254fd 100644 --- a/libraries/render/src/render/drawCellBounds.slv +++ b/libraries/render/src/render/drawCellBounds.slv @@ -20,8 +20,7 @@ <$declareColorWheel()$> <@include SceneOctree.slh@> -layout(location=GPU_UNIFORM_EXTRA0) uniform ivec4 inCellLocation; - +layout(location=0) in ivec4 inCellLocation; layout(location=0) out vec4 varColor; void main(void) { @@ -62,5 +61,5 @@ void main(void) { TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, pos, gl_Position)$> - varColor = vec4(colorWheel(fract(float(inCellLocation.w) / 5.0)), 0.8 + 0.2 * cellIsEmpty); + varColor = vec4(colorWheel(fract(float(inCellLocation.w) / 5.0)), 0.8 + 0.2 * float(cellIsEmpty)); } \ No newline at end of file diff --git a/libraries/render/src/render/drawItemBounds.slv b/libraries/render/src/render/drawItemBounds.slv index 8925009160..ea4d0f24e6 100644 --- a/libraries/render/src/render/drawItemBounds.slv +++ b/libraries/render/src/render/drawItemBounds.slv @@ -20,7 +20,14 @@ <@include gpu/Color.slh@> <$declareColorWheel()$> -layout(location=GPU_UNIFORM_COLOR) uniform vec4 inColor; +struct DrawItemBoundsParams { + vec4 color; +}; + +layout(binding=0) uniform drawItemBoundsParamsBuffer { + DrawItemBoundsParams params; +}; + struct ItemBound { vec4 id_boundPos; @@ -91,10 +98,10 @@ void main(void) { TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, pos, gl_Position)$> - if (inColor.w < 0.0) { - varColor = vec4(colorWheel(float(boundID)/(-inColor.w)), 1.0); + if (params.color.w < 0.0) { + varColor = vec4(colorWheel(float(boundID)/(-params.color.w)), 1.0); } else { - varColor = vec4(colorWheel(float(inColor.w)), 1.0); + varColor = vec4(colorWheel(float(params.color.w)), 1.0); } varTexcoord = vec2(cubeVec.w, length(boundDim)); diff --git a/libraries/render/src/render/drawItemStatus.slf b/libraries/render/src/render/drawItemStatus.slf index 309a73ae15..9409ee6171 100644 --- a/libraries/render/src/render/drawItemStatus.slf +++ b/libraries/render/src/render/drawItemStatus.slf @@ -18,7 +18,7 @@ layout(location=0) out vec4 outFragColor; layout(binding=0) uniform sampler2D _icons; vec2 getIconTexcoord(float icon, vec2 uv) { const vec2 ICON_COORD_SIZE = vec2(0.0625, 1.0); - return vec2((uv.x + icon) * ICON_COORD_SIZE.x, uv.y * ICON_COORD_SIZE.y); + return vec2((uv.x + icon) * ICON_COORD_SIZE.x, uv.y * ICON_COORD_SIZE.y); } void main(void) { diff --git a/libraries/render/src/render/drawItemStatus.slv b/libraries/render/src/render/drawItemStatus.slv index e92bdda248..7aac26fe2e 100644 --- a/libraries/render/src/render/drawItemStatus.slv +++ b/libraries/render/src/render/drawItemStatus.slv @@ -20,10 +20,10 @@ layout(location=0) out vec4 varColor; layout(location=1) out vec3 varTexcoord; -layout(location=GPU_UNIFORM_EXTRA0) uniform vec3 inBoundPos; -layout(location=GPU_UNIFORM_EXTRA1) uniform vec3 inBoundDim; -layout(location=GPU_UNIFORM_EXTRA2) uniform ivec4 inStatus0; -layout(location=GPU_UNIFORM_EXTRA3) uniform ivec4 inStatus1; +layout(location=0) in vec3 inBoundPos; +layout(location=1) in vec3 inBoundDim; +layout(location=2) in ivec4 inStatus0; +layout(location=3) in ivec4 inStatus1; vec3 paintRainbow(float normalizedHue) { float v = normalizedHue * 6.f; diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index 72d6901fb5..0e05a563b2 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -108,6 +108,10 @@ public: Q_INVOKABLE void setBakingEnabled(QString path, bool enabled, QScriptValue callback); #if (PR_BUILD || DEV_BUILD) + /** + * This function is purely for development purposes, and not meant for use in a + * production context. It is not a public-facing API, so it should not contain jsdoc. + */ Q_INVOKABLE void sendFakedHandshake(); #endif diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index ce4ec89950..cfd155e14b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -271,7 +271,7 @@ void ScriptEngine::disconnectNonEssentialSignals() { // Ensure the thread should be running, and does exist if (_isRunning && _isThreaded && (workerThread = thread())) { connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit); - connect(this, &QObject::destroyed, workerThread, &QObject::deleteLater); + connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); } } @@ -386,7 +386,7 @@ void ScriptEngine::runInThread() { // disconnectNonEssentialSignals() method connect(workerThread, &QThread::started, this, &ScriptEngine::run); connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit); - connect(this, &QObject::destroyed, workerThread, &QObject::deleteLater); + connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); workerThread->start(); } @@ -1974,8 +1974,9 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin } int ScriptEngine::getNumRunningEntityScripts() const { + QReadLocker locker { &_entityScriptsLock }; int sum = 0; - for (auto& st : _entityScripts) { + for (const auto& st : _entityScripts) { if (st.status == EntityScriptStatus::RUNNING) { ++sum; } @@ -1984,14 +1985,20 @@ int ScriptEngine::getNumRunningEntityScripts() const { } void ScriptEngine::setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details) { - _entityScripts[entityID] = details; + { + QWriteLocker locker { &_entityScriptsLock }; + _entityScripts[entityID] = details; + } emit entityScriptDetailsUpdated(); } void ScriptEngine::updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus &status, const QString& errorInfo) { - EntityScriptDetails &details = _entityScripts[entityID]; - details.status = status; - details.errorInfo = errorInfo; + { + QWriteLocker locker { &_entityScriptsLock }; + EntityScriptDetails& details = _entityScripts[entityID]; + details.status = status; + details.errorInfo = errorInfo; + } emit entityScriptDetailsUpdated(); } @@ -2042,6 +2049,7 @@ QFuture ScriptEngine::getLocalEntityScriptDetails(const EntityItemID& } bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const { + QReadLocker locker { &_entityScriptsLock }; auto it = _entityScripts.constFind(entityID); if (it == _entityScripts.constEnd()) { return false; @@ -2050,6 +2058,11 @@ bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntitySc return true; } +bool ScriptEngine::hasEntityScriptDetails(const EntityItemID& entityID) const { + QReadLocker locker { &_entityScriptsLock }; + return _entityScripts.contains(entityID); +} + const static EntityItemID BAD_SCRIPT_UUID_PLACEHOLDER { "{20170224-dead-face-0000-cee000021114}" }; void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const EntityItemID& leaderID) { @@ -2064,14 +2077,15 @@ void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const } foreach(DeferredLoadEntity retry, retryLoads) { // check whether entity was since been deleted - if (!_entityScripts.contains(retry.entityID)) { + + EntityScriptDetails details; + if (!getEntityScriptDetails(retry.entityID, details)) { qCDebug(scriptengine) << "processDeferredEntityLoads -- entity details gone (entity deleted?)" << retry.entityID; continue; } // check whether entity has since been unloaded or otherwise errored-out - auto details = _entityScripts[retry.entityID]; if (details.status != EntityScriptStatus::PENDING) { qCDebug(scriptengine) << "processDeferredEntityLoads -- entity status no longer PENDING; " << retry.entityID << details.status; @@ -2079,7 +2093,11 @@ void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const } // propagate leader's failure reasons to the pending entity - const auto leaderDetails = _entityScripts[leaderID]; + EntityScriptDetails leaderDetails; + { + QWriteLocker locker { &_entityScriptsLock }; + leaderDetails = _entityScripts[leaderID]; + } if (leaderDetails.status != EntityScriptStatus::RUNNING) { qCDebug(scriptengine) << QString("... pending load of %1 cancelled (leader: %2 status: %3)") .arg(retry.entityID.toString()).arg(leaderID.toString()).arg(leaderDetails.status); @@ -2125,7 +2143,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& return; } - if (!_entityScripts.contains(entityID)) { + if (!hasEntityScriptDetails(entityID)) { // make sure EntityScriptDetails has an entry for this UUID right away // (which allows bailing from the loading/provisioning process early if the Entity gets deleted mid-flight) updateEntityScriptStatus(entityID, EntityScriptStatus::PENDING, "...pending..."); @@ -2166,9 +2184,12 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& _occupiedScriptURLs[entityScript] = entityID; #ifdef DEBUG_ENTITY_STATES - auto previousStatus = _entityScripts.contains(entityID) ? _entityScripts[entityID].status : EntityScriptStatus::PENDING; - qCDebug(scriptengine) << "loadEntityScript.LOADING: " << entityScript << entityID.toString() - << "(previous: " << previousStatus << ")"; + { + EntityScriptDetails details; + bool hasEntityScript = getEntityScriptDetails(entityID, details); + qCDebug(scriptengine) << "loadEntityScript.LOADING: " << entityScript << entityID.toString() + << "(previous: " << (hasEntityScript ? details.status : EntityScriptStatus::PENDING) << ")"; + } #endif EntityScriptDetails newDetails; @@ -2197,7 +2218,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& #ifdef DEBUG_ENTITY_STATES qCDebug(scriptengine) << "loadEntityScript.contentAvailable" << status << QUrl(url).fileName() << entityID.toString(); #endif - if (!isStopping() && _entityScripts.contains(entityID)) { + if (!isStopping() && hasEntityScriptDetails(entityID)) { _contentAvailableQueue[entityID] = { entityID, url, contents, isURL, success, status }; } else { #ifdef DEBUG_ENTITY_STATES @@ -2267,8 +2288,11 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co bool isFileUrl = isURL && scriptOrURL.startsWith("file://"); auto fileName = isURL ? scriptOrURL : "about:EmbeddedEntityScript"; - const EntityScriptDetails &oldDetails = _entityScripts[entityID]; - const QString entityScript = oldDetails.scriptText; + QString entityScript; + { + QWriteLocker locker { &_entityScriptsLock }; + entityScript = _entityScripts[entityID].scriptText; + } EntityScriptDetails newDetails; newDetails.scriptText = scriptOrURL; @@ -2446,8 +2470,8 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR "entityID:" << entityID; #endif - if (_entityScripts.contains(entityID)) { - const EntityScriptDetails &oldDetails = _entityScripts[entityID]; + EntityScriptDetails oldDetails; + if (getEntityScriptDetails(entityID, oldDetails)) { auto scriptText = oldDetails.scriptText; if (isEntityScriptRunning(entityID)) { @@ -2460,7 +2484,10 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR #endif if (shouldRemoveFromMap) { // this was a deleted entity, we've been asked to remove it from the map - _entityScripts.remove(entityID); + { + QWriteLocker locker { &_entityScriptsLock }; + _entityScripts.remove(entityID); + } emit entityScriptDetailsUpdated(); } else if (oldDetails.status != EntityScriptStatus::UNLOADED) { EntityScriptDetails newDetails; @@ -2491,10 +2518,19 @@ void ScriptEngine::unloadAllEntityScripts() { #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]"; #endif - foreach(const EntityItemID& entityID, _entityScripts.keys()) { + + QList keys; + { + QReadLocker locker{ &_entityScriptsLock }; + keys = _entityScripts.keys(); + } + foreach(const EntityItemID& entityID, keys) { unloadEntityScript(entityID); } - _entityScripts.clear(); + { + QWriteLocker locker{ &_entityScriptsLock }; + _entityScripts.clear(); + } emit entityScriptDetailsUpdated(); _occupiedScriptURLs.clear(); @@ -2508,7 +2544,7 @@ void ScriptEngine::unloadAllEntityScripts() { } void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { - if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !_entityScripts.contains(entityID)) { + if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !hasEntityScriptDetails(entityID)) { return; } @@ -2518,7 +2554,11 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { } recurseGuard = true; - EntityScriptDetails details = _entityScripts[entityID]; + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } // Check to see if a file based script needs to be reloaded (easier debugging) if (details.lastModified > 0) { QString filePath = QUrl(details.scriptText).toLocalFile(); @@ -2584,7 +2624,11 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { - EntityScriptDetails details = _entityScripts[entityID]; + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } QScriptValue entityScript = details.scriptObject; // previously loaded // If this is a remote call, we need to check to see if the function is remotely callable @@ -2646,7 +2690,11 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { - EntityScriptDetails details = _entityScripts[entityID]; + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } QScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { QScriptValueList args; @@ -2680,7 +2728,11 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { - EntityScriptDetails details = _entityScripts[entityID]; + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } QScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { QScriptValueList args; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 94b50bfd2c..08e2c492e8 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -461,7 +461,9 @@ public: * @returns {boolean} */ Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) { - return _entityScripts.contains(entityID) && _entityScripts[entityID].status == EntityScriptStatus::RUNNING; + QReadLocker locker { &_entityScriptsLock }; + auto it = _entityScripts.constFind(entityID); + return it != _entityScripts.constEnd() && it->status == EntityScriptStatus::RUNNING; } QVariant cloneEntityScriptDetails(const EntityItemID& entityID); QFuture getLocalEntityScriptDetails(const EntityItemID& entityID) override; @@ -559,6 +561,7 @@ public: void clearDebugLogWindow(); int getNumRunningEntityScripts() const; bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; + bool hasEntityScriptDetails(const EntityItemID& entityID) const; public slots: @@ -771,6 +774,7 @@ protected: bool _isInitialized { false }; QHash _timerFunctionMap; QSet _includedURLs; + mutable QReadWriteLock _entityScriptsLock { QReadWriteLock::Recursive }; QHash _entityScripts; QHash _occupiedScriptURLs; QList _deferredEntityLoads; diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index 994df551fe..ff6c2a4e6e 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -79,33 +79,43 @@ void AABox::setBox(const glm::vec3& corner, const glm::vec3& scale) { glm::vec3 AABox::getFarthestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; - if (normal.x > 0.0f) { - result.x += _scale.x; - } - if (normal.y > 0.0f) { - result.y += _scale.y; - } - if (normal.z > 0.0f) { - result.z += _scale.z; - } + // This is a branchless version of: + //if (normal.x > 0.0f) { + // result.x += _scale.x; + //} + //if (normal.y > 0.0f) { + // result.y += _scale.y; + //} + //if (normal.z > 0.0f) { + // result.z += _scale.z; + //} + float blend = (float)(normal.x > 0.0f); + result.x += blend * _scale.x + (1.0f - blend) * 0.0f; + blend = (float)(normal.y > 0.0f); + result.y += blend * _scale.y + (1.0f - blend) * 0.0f; + blend = (float)(normal.z > 0.0f); + result.z += blend * _scale.z + (1.0f - blend) * 0.0f; return result; } glm::vec3 AABox::getNearestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; - - if (normal.x < 0.0f) { - result.x += _scale.x; - } - - if (normal.y < 0.0f) { - result.y += _scale.y; - } - - if (normal.z < 0.0f) { - result.z += _scale.z; - } - + // This is a branchless version of: + //if (normal.x < 0.0f) { + // result.x += _scale.x; + //} + //if (normal.y < 0.0f) { + // result.y += _scale.y; + //} + //if (normal.z < 0.0f) { + // result.z += _scale.z; + //} + float blend = (float)(normal.x < 0.0f); + result.x += blend * _scale.x + (1.0f - blend) * 0.0f; + blend = (float)(normal.y < 0.0f); + result.y += blend * _scale.y + (1.0f - blend) * 0.0f; + blend = (float)(normal.z < 0.0f); + result.z += blend * _scale.z + (1.0f - blend) * 0.0f; return result; } @@ -192,9 +202,9 @@ bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& e isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x)); } -bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) const { - return findRayAABoxIntersection(origin, direction, _corner, _scale, distance, face, surfaceNormal); +bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, + float& distance, BoxFace& face, glm::vec3& surfaceNormal) const { + return findRayAABoxIntersection(origin, direction, invDirection, _corner, _scale, distance, face, surfaceNormal); } bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, @@ -459,28 +469,6 @@ AABox AABox::clamp(float min, float max) const { return AABox(clampedCorner, clampedScale); } -AABox& AABox::operator += (const glm::vec3& point) { - - if (isInvalid()) { - _corner = glm::min(_corner, point); - } else { - glm::vec3 maximum(_corner + _scale); - _corner = glm::min(_corner, point); - maximum = glm::max(maximum, point); - _scale = maximum - _corner; - } - - return (*this); -} - -AABox& AABox::operator += (const AABox& box) { - if (!box.isInvalid()) { - (*this) += box._corner; - (*this) += box.calcTopFarLeft(); - } - return (*this); -} - void AABox::embiggen(float scale) { _corner += scale * (-0.5f * _scale); _scale *= scale; diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index fbc90cff47..f41bb8a814 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -69,7 +69,7 @@ public: bool expandedContains(const glm::vec3& point, float expansion) const; bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, float& distance, BoxFace& face, glm::vec3& surfaceNormal) const; bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const; @@ -85,8 +85,23 @@ public: AABox clamp(const glm::vec3& min, const glm::vec3& max) const; AABox clamp(float min, float max) const; - AABox& operator += (const glm::vec3& point); - AABox& operator += (const AABox& box); + inline AABox& operator+=(const glm::vec3& point) { + bool valid = !isInvalid(); + glm::vec3 maximum = glm::max(_corner + _scale, point); + _corner = glm::min(_corner, point); + if (valid) { + _scale = maximum - _corner; + } + return (*this); + } + + inline AABox& operator+=(const AABox& box) { + if (!box.isInvalid()) { + (*this) += box._corner; + (*this) += box.calcTopFarLeft(); + } + return (*this); + } // Translate the AABox just moving the corner void translate(const glm::vec3& translation) { _corner += translation; } @@ -114,7 +129,7 @@ public: static const glm::vec3 INFINITY_VECTOR; - bool isInvalid() const { return _corner == INFINITY_VECTOR; } + bool isInvalid() const { return _corner.x == std::numeric_limits::infinity(); } void clear() { _corner = INFINITY_VECTOR; _scale = glm::vec3(0.0f); } diff --git a/libraries/shared/src/AACube.cpp b/libraries/shared/src/AACube.cpp index dc1003215d..af2e531917 100644 --- a/libraries/shared/src/AACube.cpp +++ b/libraries/shared/src/AACube.cpp @@ -187,9 +187,9 @@ bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x)); } -bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) const { - return findRayAABoxIntersection(origin, direction, _corner, glm::vec3(_scale), distance, face, surfaceNormal); +bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, + float& distance, BoxFace& face, glm::vec3& surfaceNormal) const { + return findRayAABoxIntersection(origin, direction, invDirection, _corner, glm::vec3(_scale), distance, face, surfaceNormal); } bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, diff --git a/libraries/shared/src/AACube.h b/libraries/shared/src/AACube.h index 72aed31999..66b29e3185 100644 --- a/libraries/shared/src/AACube.h +++ b/libraries/shared/src/AACube.h @@ -56,10 +56,10 @@ public: bool touches(const AABox& otherBox) const; bool expandedContains(const glm::vec3& point, float expansion) const; bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) const; + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, + float& distance, BoxFace& face, glm::vec3& surfaceNormal) const; bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const; + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const; bool touchesSphere(const glm::vec3& center, float radius) const; bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const; bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const; diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 39117de7ef..6c38d08c96 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -34,7 +34,7 @@ const float DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 3.3f; const float DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD = 0.18f; const float DEFAULT_HEAD_PITCH_STEPPING_TOLERANCE = 7.0f; const float DEFAULT_HEAD_ROLL_STEPPING_TOLERANCE = 7.0f; -const float DEFAULT_AVATAR_SPINE_STRETCH_LIMIT = 0.07f; +const float DEFAULT_AVATAR_SPINE_STRETCH_LIMIT = 0.04f; const float DEFAULT_AVATAR_FORWARD_DAMPENING_FACTOR = 0.5f; const float DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR = 2.0f; const float DEFAULT_AVATAR_HIPS_MASS = 40.0f; @@ -44,7 +44,7 @@ const float DEFAULT_AVATAR_RIGHTHAND_MASS = 2.0f; // Used when avatar is missing joints... (avatar space) const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; -const glm::vec3 DEFAULT_AVATAR_MIDDLE_EYE_POS { 0.0f, 0.6f, 0.0f }; +const glm::vec3 DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET = { 0.0f, 0.06f, -0.09f }; const glm::vec3 DEFAULT_AVATAR_HEAD_POS { 0.0f, 0.53f, 0.0f }; const glm::quat DEFAULT_AVATAR_HEAD_ROT { Quaternions::Y_180 }; const glm::vec3 DEFAULT_AVATAR_RIGHTARM_POS { -0.134824f, 0.396348f, -0.0515777f }; @@ -68,6 +68,7 @@ const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { -0.4016716778278351f, 0.915461599 const float DEFAULT_AVATAR_MAX_WALKING_SPEED = 2.6f; // meters / second const float DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED = 2.2f; // meters / second const float DEFAULT_AVATAR_MAX_FLYING_SPEED = 30.0f; // meters / second +const float DEFAULT_AVATAR_WALK_SPEED_THRESHOLD = 0.15f; const float DEFAULT_AVATAR_GRAVITY = -5.0f; // meters / second^2 const float DEFAULT_AVATAR_JUMP_SPEED = 3.5f; // meters / second diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 7e6ef4cb28..96219ea48c 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -316,4 +316,17 @@ inline void glm_mat4u_mul(const glm::mat4& m1, const glm::mat4& m2, glm::mat4& r #endif } +// convert float to int, using round-to-nearest-even (undefined on overflow) +inline int fastLrintf(float x) { +#if GLM_ARCH & GLM_ARCH_SSE2_BIT + return _mm_cvt_ss2si(_mm_set_ss(x)); +#else + // return lrintf(x); + static_assert(std::numeric_limits::is_iec559, "Requires IEEE-754 double precision format"); + union { double d; int64_t i; } bits = { (double)x }; + bits.d += (3ULL << 51); + return (int)bits.i; +#endif +} + #endif // hifi_GLMHelpers_h diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 6fb06eb624..3f2f7cd7fb 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -214,65 +214,39 @@ bool findInsideOutIntersection(float origin, float direction, float corner, floa return false; } -bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& corner, const glm::vec3& scale, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) { - // handle the trivial case where the box contains the origin - if (aaBoxContains(origin, corner, scale)) { - // We still want to calculate the distance from the origin to the inside out plane - float axisDistance; - if ((findInsideOutIntersection(origin.x, direction.x, corner.x, scale.x, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && - isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { - distance = axisDistance; - face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.y, direction.y, corner.y, scale.y, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x) && - isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { - distance = axisDistance; - face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.z, direction.z, corner.z, scale.z, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && - isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x))) { - distance = axisDistance; - face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f); - return true; - } - // This case is unexpected, but mimics the previous behavior for inside out intersections - distance = 0; - return true; +// https://tavianator.com/fast-branchless-raybounding-box-intersections/ +bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, + const glm::vec3& corner, const glm::vec3& scale, float& distance, BoxFace& face, glm::vec3& surfaceNormal) { + float t1, t2, newTmin, newTmax, tmin = -INFINITY, tmax = INFINITY; + int minAxis = -1, maxAxis = -1; + + for (int i = 0; i < 3; ++i) { + t1 = (corner[i] - origin[i]) * invDirection[i]; + t2 = (corner[i] + scale[i] - origin[i]) * invDirection[i]; + + newTmin = glm::min(t1, t2); + newTmax = glm::max(t1, t2); + + minAxis = newTmin > tmin ? i : minAxis; + tmin = glm::max(tmin, newTmin); + maxAxis = newTmax < tmax ? i : maxAxis; + tmax = glm::min(tmax, newTmax); } - // check each axis - float axisDistance; - if ((findIntersection(origin.x, direction.x, corner.x, scale.x, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && - isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { - distance = axisDistance; - face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? -1.0f : 1.0f, 0.0f, 0.0f); - return true; - } - if ((findIntersection(origin.y, direction.y, corner.y, scale.y, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x) && - isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { - distance = axisDistance; - face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? -1.0f : 1.0f, 0.0f); - return true; - } - if ((findIntersection(origin.z, direction.z, corner.z, scale.z, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && - isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x))) { - distance = axisDistance; - face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? -1.0f : 1.0f); + if (tmax >= glm::max(tmin, 0.0f)) { + if (tmin < 0.0f) { + distance = tmax; + bool positiveDirection = direction[maxAxis] > 0.0f; + surfaceNormal = glm::vec3(0.0f); + surfaceNormal[maxAxis] = positiveDirection ? -1.0f : 1.0f; + face = positiveDirection ? BoxFace(2 * maxAxis + 1) : BoxFace(2 * maxAxis); + } else { + distance = tmin; + bool positiveDirection = direction[minAxis] > 0.0f; + surfaceNormal = glm::vec3(0.0f); + surfaceNormal[minAxis] = positiveDirection ? -1.0f : 1.0f; + face = positiveDirection ? BoxFace(2 * minAxis) : BoxFace(2 * minAxis + 1); + } return true; } return false; @@ -286,12 +260,13 @@ bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& directi distance = 0.0f; return true; // starts inside the sphere } - float b = glm::dot(direction, relativeOrigin); - float radicand = b * b - c; + float b = 2.0f * glm::dot(direction, relativeOrigin); + float a = glm::dot(direction, direction); + float radicand = b * b - 4.0f * a * c; if (radicand < 0.0f) { return false; // doesn't hit the sphere } - float t = -b - sqrtf(radicand); + float t = 0.5f * (-b - sqrtf(radicand)) / a; if (t < 0.0f) { return false; // doesn't hit the sphere } @@ -391,24 +366,34 @@ Triangle Triangle::operator*(const glm::mat4& transform) const { }; } +// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface) { - glm::vec3 firstSide = v0 - v1; - glm::vec3 secondSide = v2 - v1; - glm::vec3 normal = glm::cross(secondSide, firstSide); - float dividend = glm::dot(normal, v1) - glm::dot(origin, normal); - if (!allowBackface && dividend > 0.0f) { - return false; // origin below plane - } - float divisor = glm::dot(normal, direction); - if (divisor >= 0.0f) { + glm::vec3 firstSide = v1 - v0; + glm::vec3 secondSide = v2 - v0; + glm::vec3 P = glm::cross(direction, secondSide); + float det = glm::dot(firstSide, P); + if (!allowBackface && det < EPSILON) { + return false; + } else if (fabsf(det) < EPSILON) { return false; } - float t = dividend / divisor; - glm::vec3 point = origin + direction * t; - if (glm::dot(normal, glm::cross(point - v1, firstSide)) > 0.0f && - glm::dot(normal, glm::cross(secondSide, point - v1)) > 0.0f && - glm::dot(normal, glm::cross(point - v0, v2 - v0)) > 0.0f) { + + float invDet = 1.0f / det; + glm::vec3 T = origin - v0; + float u = glm::dot(T, P) * invDet; + if (u < 0.0f || u > 1.0f) { + return false; + } + + glm::vec3 Q = glm::cross(T, firstSide); + float v = glm::dot(direction, Q) * invDet; + if (v < 0.0f || u + v > 1.0f) { + return false; + } + + float t = glm::dot(secondSide, Q) * invDet; + if (t > EPSILON) { distance = t; return true; } diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index 54f9062469..8ec75f71bd 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -76,8 +76,8 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& bool findIntersection(float origin, float direction, float corner, float size, float& distance); bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance); -bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& corner, const glm::vec3& scale, float& distance, - BoxFace& face, glm::vec3& surfaceNormal); +bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, + const glm::vec3& corner, const glm::vec3& scale, float& distance, BoxFace& face, glm::vec3& surfaceNormal); bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& center, float radius, float& distance); diff --git a/libraries/shared/src/NumericalConstants.h b/libraries/shared/src/NumericalConstants.h index 4c24a73226..8377c48960 100644 --- a/libraries/shared/src/NumericalConstants.h +++ b/libraries/shared/src/NumericalConstants.h @@ -36,6 +36,7 @@ const float METERS_PER_MILLIMETER = 0.001f; const float MILLIMETERS_PER_METER = 1000.0f; const quint64 NSECS_PER_USEC = 1000; const quint64 NSECS_PER_MSEC = 1000000; +const quint64 NSECS_PER_SECOND = 1e9; const quint64 USECS_PER_MSEC = 1000; const quint64 MSECS_PER_SECOND = 1000; const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND; diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 34ec074d45..27f6b193ba 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -12,60 +12,20 @@ #define hifi_PrioritySortUtil_h #include -#include #include "NumericalConstants.h" #include "shared/ConicalViewFrustum.h" -/* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use: +// PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. -(1) Derive a class from pure-virtual PrioritySortUtil::Sortable that wraps a copy of - the Thing you want to prioritize and sort: - - class SortableWrapper: public PrioritySortUtil::Sortable { - public: - SortableWrapper(const Thing& thing) : _thing(thing) { } - glm::vec3 getPosition() const override { return _thing->getPosition(); } - float getRadius() const override { return 0.5f * _thing->getBoundingRadius(); } - uint64_t getTimestamp() const override { return _thing->getLastTime(); } - Thing getThing() const { return _thing; } - private: - Thing _thing; - }; - -(2) Make a PrioritySortUtil::PriorityQueue and add them to the queue: - - PrioritySortUtil::PriorityQueue sortedThings(viewFrustum); - std::priority_queue< PrioritySortUtil::Sortable > sortedThings; - for (thing in things) { - sortedThings.push(SortableWrapper(thing)); - } - -(3) Loop over your priority queue and do timeboxed work: - - NOTE: Be careful using references to members of instances of T from std::priority_queue. - Under the hood std::priority_queue may re-use instances of T. - For example, after a pop() or a push() the top T may have the same memory address - as the top T before the pop() or push() (but point to a swapped instance of T). - This causes a reference to member variable of T to point to a different value - when operations taken on std::priority_queue shuffle around the instances of T. - - uint64_t cutoffTime = usecTimestampNow() + TIME_BUDGET; - while (!sortedThings.empty()) { - const Thing& thing = sortedThings.top(); - // ...do work on thing... - sortedThings.pop(); - if (usecTimestampNow() > cutoffTime) { - break; - } - } -*/ +const float OUT_OF_VIEW_PENALTY = -10.0f; +const float OUT_OF_VIEW_THRESHOLD = 0.5f * OUT_OF_VIEW_PENALTY; namespace PrioritySortUtil { constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; constexpr float DEFAULT_CENTER_COEF { 0.5f }; - constexpr float DEFAULT_AGE_COEF { 0.25f / (float)(USECS_PER_SECOND) }; + constexpr float DEFAULT_AGE_COEF { 0.25f }; class Sortable { public: @@ -75,7 +35,6 @@ namespace PrioritySortUtil { void setPriority(float priority) { _priority = priority; } float getPriority() const { return _priority; } - bool operator<(const Sortable& other) const { return _priority < other._priority; } private: float _priority { 0.0f }; }; @@ -86,8 +45,9 @@ namespace PrioritySortUtil { PriorityQueue() = delete; PriorityQueue(const ConicalViewFrustums& views) : _views(views) { } PriorityQueue(const ConicalViewFrustums& views, float angularWeight, float centerWeight, float ageWeight) - : _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) - { } + : _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) + , _usecCurrentTime(usecTimestampNow()) { + } void setViews(const ConicalViewFrustums& views) { _views = views; } @@ -95,16 +55,21 @@ namespace PrioritySortUtil { _angularWeight = angularWeight; _centerWeight = centerWeight; _ageWeight = ageWeight; + _usecCurrentTime = usecTimestampNow(); } - size_t size() const { return _queue.size(); } + size_t size() const { return _vector.size(); } void push(T thing) { thing.setPriority(computePriority(thing)); - _queue.push(thing); + _vector.push_back(thing); + } + void reserve(size_t num) { + _vector.reserve(num); + } + const std::vector& getSortedVector() { + std::sort(_vector.begin(), _vector.end(), [](const T& left, const T& right) { return left.getPriority() > right.getPriority(); }); + return _vector; } - const T& top() const { return _queue.top(); } - void pop() { return _queue.pop(); } - bool empty() const { return _queue.empty(); } private: @@ -129,23 +94,18 @@ namespace PrioritySortUtil { glm::vec3 offset = position - view.getPosition(); float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero const float MIN_RADIUS = 0.1f; // WORKAROUND for zero size objects (we still want them to sort by distance) - float radius = glm::min(thing.getRadius(), MIN_RADIUS); - float cosineAngle = (glm::dot(offset, view.getDirection()) / distance); - float age = (float)(usecTimestampNow() - thing.getTimestamp()); + float radius = glm::max(thing.getRadius(), MIN_RADIUS); + // Other item's angle from view centre: + float cosineAngle = glm::dot(offset, view.getDirection()) / distance; + float age = float((_usecCurrentTime - thing.getTimestamp()) / USECS_PER_SECOND); - // we modulatate "age" drift rate by the cosineAngle term to make periphrial objects sort forward - // at a reduced rate but we don't want the "age" term to go zero or negative so we clamp it - const float MIN_COSINE_ANGLE_FACTOR = 0.1f; - float cosineAngleFactor = glm::max(cosineAngle, MIN_COSINE_ANGLE_FACTOR); - - float priority = _angularWeight * glm::max(radius, MIN_RADIUS) / distance - + _centerWeight * cosineAngle - + _ageWeight * cosineAngleFactor * age; + // the "age" term accumulates at the sum of all weights + float angularSize = radius / distance; + float priority = (_angularWeight * angularSize + _centerWeight * cosineAngle) * (age + 1.0f) + _ageWeight * age; // decrement priority of things outside keyhole if (distance - radius > view.getRadius()) { if (!view.intersects(offset, distance, radius)) { - constexpr float OUT_OF_VIEW_PENALTY = -10.0f; priority += OUT_OF_VIEW_PENALTY; } } @@ -153,16 +113,17 @@ namespace PrioritySortUtil { } ConicalViewFrustums _views; - std::priority_queue _queue; + std::vector _vector; float _angularWeight { DEFAULT_ANGULAR_COEF }; float _centerWeight { DEFAULT_CENTER_COEF }; float _ageWeight { DEFAULT_AGE_COEF }; + quint64 _usecCurrentTime { 0 }; }; } // namespace PrioritySortUtil -// for now we're keeping hard-coded sorted time budgets in one spot + // for now we're keeping hard-coded sorted time budgets in one spot const uint64_t MAX_UPDATE_RENDERABLES_TIME_BUDGET = 2000; // usec const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1000; // usec +const uint64_t MAX_UPDATE_AVATARS_TIME_BUDGET = 2000; // usec #endif // hifi_PrioritySortUtil_h - diff --git a/libraries/shared/src/SettingManager.cpp b/libraries/shared/src/SettingManager.cpp index 2e0850255a..e5920b785e 100644 --- a/libraries/shared/src/SettingManager.cpp +++ b/libraries/shared/src/SettingManager.cpp @@ -52,7 +52,7 @@ namespace Setting { if (_pendingChanges.contains(key) && _pendingChanges[key] != UNSET_VALUE) { loadedValue = _pendingChanges[key]; } else { - loadedValue = value(key); + loadedValue = _qSettings.value(key); } if (loadedValue.isValid()) { handle->setVariant(loadedValue); @@ -92,32 +92,115 @@ namespace Setting { } void Manager::saveAll() { - bool forceSync = false; withWriteLock([&] { + bool forceSync = false; for (auto key : _pendingChanges.keys()) { auto newValue = _pendingChanges[key]; - auto savedValue = value(key, UNSET_VALUE); + auto savedValue = _qSettings.value(key, UNSET_VALUE); if (newValue == savedValue) { continue; } + forceSync = true; if (newValue == UNSET_VALUE || !newValue.isValid()) { - forceSync = true; - remove(key); + _qSettings.remove(key); } else { - forceSync = true; - setValue(key, newValue); + _qSettings.setValue(key, newValue); } } _pendingChanges.clear(); - }); - if (forceSync) { - sync(); - } + if (forceSync) { + _qSettings.sync(); + } + }); // Restart timer if (_saveTimer) { _saveTimer->start(); } } + + QString Manager::fileName() const { + return resultWithReadLock([&] { + return _qSettings.fileName(); + }); + } + + void Manager::remove(const QString &key) { + withWriteLock([&] { + _qSettings.remove(key); + }); + } + + QStringList Manager::childGroups() const { + return resultWithReadLock([&] { + return _qSettings.childGroups(); + }); + } + + QStringList Manager::childKeys() const { + return resultWithReadLock([&] { + return _qSettings.childKeys(); + }); + } + + QStringList Manager::allKeys() const { + return resultWithReadLock([&] { + return _qSettings.allKeys(); + }); + } + + bool Manager::contains(const QString &key) const { + return resultWithReadLock([&] { + return _qSettings.contains(key); + }); + } + + int Manager::beginReadArray(const QString &prefix) { + return resultWithReadLock([&] { + return _qSettings.beginReadArray(prefix); + }); + } + + void Manager::beginGroup(const QString &prefix) { + withWriteLock([&] { + _qSettings.beginGroup(prefix); + }); + } + + void Manager::beginWriteArray(const QString &prefix, int size) { + withWriteLock([&] { + _qSettings.beginWriteArray(prefix, size); + }); + } + + void Manager::endArray() { + withWriteLock([&] { + _qSettings.endArray(); + }); + } + + void Manager::endGroup() { + withWriteLock([&] { + _qSettings.endGroup(); + }); + } + + void Manager::setArrayIndex(int i) { + withWriteLock([&] { + _qSettings.setArrayIndex(i); + }); + } + + void Manager::setValue(const QString &key, const QVariant &value) { + withWriteLock([&] { + _qSettings.setValue(key, value); + }); + } + + QVariant Manager::value(const QString &key, const QVariant &defaultValue) const { + return resultWithReadLock([&] { + return _qSettings.value(key, defaultValue); + }); + } } diff --git a/libraries/shared/src/SettingManager.h b/libraries/shared/src/SettingManager.h index 6696a1ecf4..49f3bece4b 100644 --- a/libraries/shared/src/SettingManager.h +++ b/libraries/shared/src/SettingManager.h @@ -23,12 +23,28 @@ namespace Setting { class Interface; - class Manager : public QSettings, public ReadWriteLockable, public Dependency { + class Manager : public QObject, public ReadWriteLockable, public Dependency { Q_OBJECT public: void customDeleter() override; + // thread-safe proxies into QSettings + QString fileName() const; + void remove(const QString &key); + QStringList childGroups() const; + QStringList childKeys() const; + QStringList allKeys() const; + bool contains(const QString &key) const; + int beginReadArray(const QString &prefix); + void beginGroup(const QString &prefix); + void beginWriteArray(const QString &prefix, int size = -1); + void endArray(); + void endGroup(); + void setArrayIndex(int i); + void setValue(const QString &key, const QVariant &value); + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + protected: ~Manager(); void registerHandle(Interface* handle); @@ -52,6 +68,9 @@ namespace Setting { friend class Interface; friend void cleanupSettingsSaveThread(); friend void setupSettingsSaveThread(); + + + QSettings _qSettings; }; } diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index cde9c20cab..02a8d458a9 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -13,6 +13,8 @@ #include "GLMHelpers.h" +#include + void TriangleSet::insert(const Triangle& t) { _isBalanced = false; @@ -27,48 +29,7 @@ void TriangleSet::clear() { _bounds.clear(); _isBalanced = false; - _triangleOctree.clear(); -} - -bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) { - - // reset our distance to be the max possible, lower level tests will store best distance here - distance = std::numeric_limits::max(); - - if (!_isBalanced) { - balanceOctree(); - } - - int trianglesTouched = 0; - auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, triangle, precision, trianglesTouched, allowBackface); - - #if WANT_DEBUGGING - if (precision) { - qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population << "_triangles.size:" << _triangles.size(); - } - #endif - return result; -} - -bool TriangleSet::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) { - // reset our distance to be the max possible, lower level tests will store best distance here - parabolicDistance = FLT_MAX; - - if (!_isBalanced) { - balanceOctree(); - } - - int trianglesTouched = 0; - auto result = _triangleOctree.findParabolaIntersection(origin, velocity, acceleration, parabolicDistance, face, triangle, precision, trianglesTouched, allowBackface); - -#if WANT_DEBUGGING - if (precision) { - qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population << "_triangles.size:" << _triangles.size(); - } -#endif - return result; + _triangleTree.clear(); } bool TriangleSet::convexHullContains(const glm::vec3& point) const { @@ -92,33 +53,158 @@ void TriangleSet::debugDump() { qDebug() << __FUNCTION__; qDebug() << "bounds:" << getBounds(); qDebug() << "triangles:" << size() << "at top level...."; - qDebug() << "----- _triangleOctree -----"; - _triangleOctree.debugDump(); + qDebug() << "----- _triangleTree -----"; + _triangleTree.debugDump(); } -void TriangleSet::balanceOctree() { - _triangleOctree.reset(_bounds, 0); +void TriangleSet::balanceTree() { + _triangleTree.reset(_bounds); // insert all the triangles for (size_t i = 0; i < _triangles.size(); i++) { - _triangleOctree.insert(i); + _triangleTree.insert(i); } _isBalanced = true; - #if WANT_DEBUGGING +#if WANT_DEBUGGING debugDump(); - #endif +#endif } +// With an octree: 8 ^ MAX_DEPTH = 4096 leaves +//static const int MAX_DEPTH = 4; +// With a k-d tree: 2 ^ MAX_DEPTH = 4096 leaves +static const int MAX_DEPTH = 12; + +TriangleSet::TriangleTreeCell::TriangleTreeCell(std::vector& allTriangles, const AABox& bounds, int depth) : + _allTriangles(allTriangles) +{ + reset(bounds, depth); +} + +void TriangleSet::TriangleTreeCell::clear() { + _population = 0; + _triangleIndices.clear(); + _bounds.clear(); + _children.first.reset(); + _children.second.reset(); +} + +void TriangleSet::TriangleTreeCell::reset(const AABox& bounds, int depth) { + clear(); + _bounds = bounds; + _depth = depth; +} + +void TriangleSet::TriangleTreeCell::debugDump() { + qDebug() << __FUNCTION__; + qDebug() << " bounds:" << getBounds(); + qDebug() << " depth:" << _depth; + qDebug() << " population:" << _population << "this level or below" + << " ---- triangleIndices:" << _triangleIndices.size() << "in this cell"; + + int numChildren = 0; + if (_children.first) { + numChildren++; + } else if (_children.second) { + numChildren++; + } + qDebug() << "child cells:" << numChildren; + if (_depth < MAX_DEPTH) { + if (_children.first) { + qDebug() << "child: 0"; + _children.first->debugDump(); + } + if (_children.second) { + qDebug() << "child: 1"; + _children.second->debugDump(); + } + } +} + +std::pair TriangleSet::TriangleTreeCell::getTriangleTreeCellChildBounds() { + std::pair toReturn; + int axis = 0; + // find biggest axis + glm::vec3 dimensions = _bounds.getDimensions(); + for (int i = 0; i < 3; i++) { + if (dimensions[i] >= dimensions[(i + 1) % 3] && dimensions[i] >= dimensions[(i + 2) % 3]) { + axis = i; + break; + } + } + + // The new boxes are half the size in the largest dimension + glm::vec3 newDimensions = dimensions; + newDimensions[axis] *= 0.5f; + toReturn.first.setBox(_bounds.getCorner(), newDimensions); + glm::vec3 offset = glm::vec3(0.0f); + offset[axis] = newDimensions[axis]; + toReturn.second.setBox(_bounds.getCorner() + offset, newDimensions); + return toReturn; +} + +void TriangleSet::TriangleTreeCell::insert(size_t triangleIndex) { + _population++; + + // if we're not yet at the max depth, then check which child the triangle fits in + if (_depth < MAX_DEPTH) { + const Triangle& triangle = _allTriangles[triangleIndex]; + auto childBounds = getTriangleTreeCellChildBounds(); + + auto insertOperator = [&](const AABox& childBound, std::shared_ptr& child) { + // if the child AABox would contain the triangle... + if (childBound.contains(triangle)) { + // if the child cell doesn't yet exist, create it... + if (!child) { + child = std::make_shared(_allTriangles, childBound, _depth + 1); + } + + // insert the triangleIndex in the child cell + child->insert(triangleIndex); + return true; + } + return false; + }; + if (insertOperator(childBounds.first, _children.first) || insertOperator(childBounds.second, _children.second)) { + return; + } + } + // either we're at max depth, or the triangle doesn't fit in one of our + // children and so we want to just record it here + _triangleIndices.push_back(triangleIndex); +} + +bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, float& distance, + BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) { + if (!_isBalanced) { + balanceTree(); + } + + float localDistance = distance; + int trianglesTouched = 0; + bool hit = _triangleTree.findRayIntersection(origin, direction, invDirection, localDistance, face, triangle, precision, trianglesTouched, allowBackface); + if (hit) { + distance = localDistance; + } + +#if WANT_DEBUGGING + if (precision) { + qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleTree._population << "_triangles.size:" << _triangles.size(); + } +#endif + return hit; +} // Determine of the given ray (origin/direction) in model space intersects with any triangles // in the set. If an intersection occurs, the distance and surface normal will be provided. -bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, +bool TriangleSet::TriangleTreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface) { bool intersectedSomething = false; float bestDistance = FLT_MAX; + Triangle bestTriangle; if (precision) { for (const auto& triangleIndex : _triangleIndices) { @@ -128,8 +214,8 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) { if (thisTriangleDistance < bestDistance) { bestDistance = thisTriangleDistance; + bestTriangle = thisTriangle; intersectedSomething = true; - triangle = thisTriangle; } } } @@ -140,17 +226,133 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec if (intersectedSomething) { distance = bestDistance; + triangle = bestTriangle; } return intersectedSomething; } -bool TriangleSet::TriangleOctreeCell::findParabolaIntersectionInternal(const glm::vec3& origin, const glm::vec3& velocity, +bool TriangleSet::TriangleTreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, + float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface) { + if (_population < 1) { + return false; // no triangles below here, so we can't intersect + } + + float bestLocalDistance = FLT_MAX; + BoxFace bestLocalFace; + Triangle bestLocalTriangle; + bool intersects = false; + + // Check our local triangle set first + // The distance passed in here is the distance to our bounding box. If !precision, that distance is used + { + float internalDistance = distance; + BoxFace internalFace; + Triangle internalTriangle; + if (findRayIntersectionInternal(origin, direction, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) { + bestLocalDistance = internalDistance; + bestLocalFace = internalFace; + bestLocalTriangle = internalTriangle; + intersects = true; + } + } + + // if we're not yet at the max depth, then check our children + if (_depth < MAX_DEPTH) { + std::list sortedTriangleCells; + auto sortingOperator = [&](std::shared_ptr& child) { + if (child) { + float priority = FLT_MAX; + if (child->getBounds().contains(origin)) { + priority = 0.0f; + } else { + float childBoundDistance = FLT_MAX; + BoxFace childBoundFace; + glm::vec3 childBoundNormal; + if (child->getBounds().findRayIntersection(origin, direction, invDirection, childBoundDistance, childBoundFace, childBoundNormal)) { + // We only need to add this cell if it's closer than the local triangle set intersection (if there was one) + if (childBoundDistance < bestLocalDistance) { + priority = childBoundDistance; + } + } + } + + if (priority < FLT_MAX) { + if (sortedTriangleCells.size() > 0 && priority < sortedTriangleCells.front().first) { + sortedTriangleCells.emplace_front(priority, child); + } else { + sortedTriangleCells.emplace_back(priority, child); + } + } + } + }; + sortingOperator(_children.first); + sortingOperator(_children.second); + + for (auto it = sortedTriangleCells.begin(); it != sortedTriangleCells.end(); ++it) { + const SortedTriangleCell& sortedTriangleCell = *it; + float childDistance = sortedTriangleCell.first; + // We can exit once childDistance > bestLocalDistance + if (childDistance > bestLocalDistance) { + break; + } + // If we're inside the child cell and !precision, we need the actual distance to the cell bounds + if (!precision && childDistance < EPSILON) { + BoxFace childBoundFace; + glm::vec3 childBoundNormal; + sortedTriangleCell.second->getBounds().findRayIntersection(origin, direction, invDirection, childDistance, childBoundFace, childBoundNormal); + } + BoxFace childFace; + Triangle childTriangle; + if (sortedTriangleCell.second->findRayIntersection(origin, direction, invDirection, childDistance, childFace, childTriangle, precision, trianglesTouched)) { + if (childDistance < bestLocalDistance) { + bestLocalDistance = childDistance; + bestLocalFace = childFace; + bestLocalTriangle = childTriangle; + intersects = true; + break; + } + } + } + } + + if (intersects) { + distance = bestLocalDistance; + face = bestLocalFace; + triangle = bestLocalTriangle; + } + return intersects; +} + +bool TriangleSet::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) { + if (!_isBalanced) { + balanceTree(); + } + + float localDistance = parabolicDistance; + int trianglesTouched = 0; + bool hit = _triangleTree.findParabolaIntersection(origin, velocity, acceleration, localDistance, face, triangle, precision, trianglesTouched, allowBackface); + if (hit) { + parabolicDistance = localDistance; + } + +#if WANT_DEBUGGING + if (precision) { + qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleTree._population << "_triangles.size:" << _triangles.size(); + } +#endif + return hit; +} + +bool TriangleSet::TriangleTreeCell::findParabolaIntersectionInternal(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface) { bool intersectedSomething = false; float bestDistance = FLT_MAX; + Triangle bestTriangle; if (precision) { for (const auto& triangleIndex : _triangleIndices) { @@ -160,8 +362,8 @@ bool TriangleSet::TriangleOctreeCell::findParabolaIntersectionInternal(const glm if (findParabolaTriangleIntersection(origin, velocity, acceleration, thisTriangle, thisTriangleDistance, allowBackface)) { if (thisTriangleDistance < bestDistance) { bestDistance = thisTriangleDistance; + bestTriangle = thisTriangle; intersectedSomething = true; - triangle = thisTriangle; } } } @@ -172,146 +374,13 @@ bool TriangleSet::TriangleOctreeCell::findParabolaIntersectionInternal(const glm if (intersectedSomething) { parabolicDistance = bestDistance; + triangle = bestTriangle; } return intersectedSomething; } -static const int MAX_DEPTH = 4; // for now -static const int MAX_CHILDREN = 8; - -TriangleSet::TriangleOctreeCell::TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth) : - _allTriangles(allTriangles) -{ - reset(bounds, depth); -} - -void TriangleSet::TriangleOctreeCell::clear() { - _population = 0; - _triangleIndices.clear(); - _bounds.clear(); - _children.clear(); -} - -void TriangleSet::TriangleOctreeCell::reset(const AABox& bounds, int depth) { - clear(); - _bounds = bounds; - _depth = depth; -} - -void TriangleSet::TriangleOctreeCell::debugDump() { - qDebug() << __FUNCTION__; - qDebug() << "bounds:" << getBounds(); - qDebug() << "depth:" << _depth; - qDebug() << "population:" << _population << "this level or below" - << " ---- triangleIndices:" << _triangleIndices.size() << "in this cell"; - - qDebug() << "child cells:" << _children.size(); - if (_depth < MAX_DEPTH) { - int childNum = 0; - for (auto& child : _children) { - qDebug() << "child:" << childNum; - child.second.debugDump(); - childNum++; - } - } -} - -void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) { - const Triangle& triangle = _allTriangles[triangleIndex]; - _population++; - // if we're not yet at the max depth, then check which child the triangle fits in - if (_depth < MAX_DEPTH) { - - for (int child = 0; child < MAX_CHILDREN; child++) { - AABox childBounds = getBounds().getOctreeChild((AABox::OctreeChild)child); - - - // if the child AABox would contain the triangle... - if (childBounds.contains(triangle)) { - // if the child cell doesn't yet exist, create it... - if (_children.find((AABox::OctreeChild)child) == _children.end()) { - _children.insert( - std::pair - ((AABox::OctreeChild)child, TriangleOctreeCell(_allTriangles, childBounds, _depth + 1))); - } - - // insert the triangleIndex in the child cell - _children.at((AABox::OctreeChild)child).insert(triangleIndex); - return; - } - } - } - // either we're at max depth, or the triangle doesn't fit in one of our - // children and so we want to just record it here - _triangleIndices.push_back(triangleIndex); -} - -bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, - bool allowBackface) { - if (_population < 1) { - return false; // no triangles below here, so we can't intersect - } - - float bestLocalDistance = FLT_MAX; - BoxFace bestLocalFace; - Triangle bestLocalTriangle; - glm::vec3 bestLocalNormal; - bool intersects = false; - - float boxDistance = FLT_MAX; - // if the pick intersects our bounding box, then continue - if (getBounds().findRayIntersection(origin, direction, boxDistance, bestLocalFace, bestLocalNormal)) { - // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) - // then we know that none of our triangles can represent a better intersection and we can return - if (boxDistance > distance) { - return false; - } - - // if we're not yet at the max depth, then check which child the triangle fits in - if (_depth < MAX_DEPTH) { - float bestChildDistance = FLT_MAX; - for (auto& child : _children) { - // check each child, if there's an intersection, it will return some distance that we need - // to compare against the other results, because there might be multiple intersections and - // we will always choose the best (shortest) intersection - float childDistance = bestChildDistance; - BoxFace childFace; - Triangle childTriangle; - if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched)) { - if (childDistance < bestLocalDistance) { - bestLocalDistance = childDistance; - bestChildDistance = childDistance; - bestLocalFace = childFace; - bestLocalTriangle = childTriangle; - intersects = true; - } - } - } - } - // also check our local triangle set - float internalDistance = boxDistance; - BoxFace internalFace; - Triangle internalTriangle; - if (findRayIntersectionInternal(origin, direction, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) { - if (internalDistance < bestLocalDistance) { - bestLocalDistance = internalDistance; - bestLocalFace = internalFace; - bestLocalTriangle = internalTriangle; - intersects = true; - } - } - } - if (intersects) { - distance = bestLocalDistance; - face = bestLocalFace; - triangle = bestLocalTriangle; - } - return intersects; -} - -bool TriangleSet::TriangleOctreeCell::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, +bool TriangleSet::TriangleTreeCell::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface) { @@ -322,52 +391,81 @@ bool TriangleSet::TriangleOctreeCell::findParabolaIntersection(const glm::vec3& float bestLocalDistance = FLT_MAX; BoxFace bestLocalFace; Triangle bestLocalTriangle; - glm::vec3 bestLocalNormal; bool intersects = false; - float boxDistance = FLT_MAX; - // if the pick intersects our bounding box, then continue - if (getBounds().findParabolaIntersection(origin, velocity, acceleration, boxDistance, bestLocalFace, bestLocalNormal)) { - // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) - // then we know that none of our triangles can represent a better intersection and we can return - if (boxDistance > parabolicDistance) { - return false; - } - - // if we're not yet at the max depth, then check which child the triangle fits in - if (_depth < MAX_DEPTH) { - float bestChildDistance = FLT_MAX; - for (auto& child : _children) { - // check each child, if there's an intersection, it will return some distance that we need - // to compare against the other results, because there might be multiple intersections and - // we will always choose the best (shortest) intersection - float childDistance = bestChildDistance; - BoxFace childFace; - Triangle childTriangle; - if (child.second.findParabolaIntersection(origin, velocity, acceleration, childDistance, childFace, childTriangle, precision, trianglesTouched)) { - if (childDistance < bestLocalDistance) { - bestLocalDistance = childDistance; - bestChildDistance = childDistance; - bestLocalFace = childFace; - bestLocalTriangle = childTriangle; - intersects = true; - } - } - } - } - // also check our local triangle set - float internalDistance = boxDistance; + // Check our local triangle set first + // The distance passed in here is the distance to our bounding box. If !precision, that distance is used + { + float internalDistance = parabolicDistance; BoxFace internalFace; Triangle internalTriangle; if (findParabolaIntersectionInternal(origin, velocity, acceleration, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) { - if (internalDistance < bestLocalDistance) { - bestLocalDistance = internalDistance; - bestLocalFace = internalFace; - bestLocalTriangle = internalTriangle; - intersects = true; + bestLocalDistance = internalDistance; + bestLocalFace = internalFace; + bestLocalTriangle = internalTriangle; + intersects = true; + } + } + + // if we're not yet at the max depth, then check our children + if (_depth < MAX_DEPTH) { + std::list sortedTriangleCells; + auto sortingOperator = [&](std::shared_ptr& child) { + if (child) { + float priority = FLT_MAX; + if (child->getBounds().contains(origin)) { + priority = 0.0f; + } else { + float childBoundDistance = FLT_MAX; + BoxFace childBoundFace; + glm::vec3 childBoundNormal; + if (child->getBounds().findParabolaIntersection(origin, velocity, acceleration, childBoundDistance, childBoundFace, childBoundNormal)) { + // We only need to add this cell if it's closer than the local triangle set intersection (if there was one) + if (childBoundDistance < bestLocalDistance) { + priority = childBoundDistance; + } + } + } + + if (priority < FLT_MAX) { + if (sortedTriangleCells.size() > 0 && priority < sortedTriangleCells.front().first) { + sortedTriangleCells.emplace_front(priority, child); + } else { + sortedTriangleCells.emplace_back(priority, child); + } + } + } + }; + sortingOperator(_children.first); + sortingOperator(_children.second); + + for (auto it = sortedTriangleCells.begin(); it != sortedTriangleCells.end(); ++it) { + const SortedTriangleCell& sortedTriangleCell = *it; + float childDistance = sortedTriangleCell.first; + // We can exit once childDistance > bestLocalDistance + if (childDistance > bestLocalDistance) { + break; + } + // If we're inside the child cell and !precision, we need the actual distance to the cell bounds + if (!precision && childDistance < EPSILON) { + BoxFace childBoundFace; + glm::vec3 childBoundNormal; + sortedTriangleCell.second->getBounds().findParabolaIntersection(origin, velocity, acceleration, childDistance, childBoundFace, childBoundNormal); + } + BoxFace childFace; + Triangle childTriangle; + if (sortedTriangleCell.second->findParabolaIntersection(origin, velocity, acceleration, childDistance, childFace, childTriangle, precision, trianglesTouched)) { + if (childDistance < bestLocalDistance) { + bestLocalDistance = childDistance; + bestLocalFace = childFace; + bestLocalTriangle = childTriangle; + intersects = true; + break; + } } } } + if (intersects) { parabolicDistance = bestLocalDistance; face = bestLocalFace; diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index 0b0d0a9ac5..3417b36b4a 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -12,23 +12,23 @@ #pragma once #include +#include #include "AABox.h" #include "GeometryUtil.h" class TriangleSet { - class TriangleOctreeCell { + class TriangleTreeCell { public: - TriangleOctreeCell(std::vector& allTriangles) : - _allTriangles(allTriangles) - { } + TriangleTreeCell(std::vector& allTriangles) : _allTriangles(allTriangles) {} + TriangleTreeCell(std::vector& allTriangles, const AABox& bounds, int depth); void insert(size_t triangleIndex); void reset(const AABox& bounds, int depth = 0); void clear(); - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface = false); bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, @@ -40,8 +40,6 @@ class TriangleSet { void debugDump(); protected: - TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth); - // checks our internal list of triangles bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, @@ -50,31 +48,33 @@ class TriangleSet { float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface = false); + std::pair getTriangleTreeCellChildBounds(); + std::vector& _allTriangles; - std::map _children; - int _depth{ 0 }; - int _population{ 0 }; + std::pair, std::shared_ptr> _children; + int _depth { 0 }; + int _population { 0 }; AABox _bounds; std::vector _triangleIndices; friend class TriangleSet; }; + using SortedTriangleCell = std::pair>; + public: - TriangleSet() : - _triangleOctree(_triangles) - {} + TriangleSet() : _triangleTree(_triangles) {} void debugDump(); void insert(const Triangle& t); - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, float& distance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false); bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false); - void balanceOctree(); + void balanceTree(); void reserve(size_t size) { _triangles.reserve(size); } // reserve space in the datastructure for size number of triangles size_t size() const { return _triangles.size(); } @@ -87,9 +87,8 @@ public: const AABox& getBounds() const { return _bounds; } protected: - - bool _isBalanced{ false }; + bool _isBalanced { false }; std::vector _triangles; - TriangleOctreeCell _triangleOctree; + TriangleTreeCell _triangleTree; AABox _bounds; }; diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp index 9bdd65bf11..0709a53602 100644 --- a/libraries/shared/src/shared/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -156,10 +156,12 @@ bool FileUtils::canCreateFile(const QString& fullPath) { qDebug(shared) << "unable to overwrite file '" << fullPath << "'"; return false; } - QDir dir(fileInfo.absolutePath()); + + QString absolutePath = fileInfo.absolutePath(); + QDir dir(absolutePath); if (!dir.exists()) { - if (!dir.mkpath(fullPath)) { - qDebug(shared) << "unable to create dir '" << dir.absolutePath() << "'"; + if (!dir.mkpath(absolutePath)) { + qDebug(shared) << "unable to create dir '" << absolutePath << "'"; return false; } } diff --git a/libraries/ui/src/InteractiveWindow.cpp b/libraries/ui/src/InteractiveWindow.cpp index 5078fcb602..6c7f2d503f 100644 --- a/libraries/ui/src/InteractiveWindow.cpp +++ b/libraries/ui/src/InteractiveWindow.cpp @@ -13,12 +13,19 @@ #include #include +#include +#include #include #include #include "OffscreenUi.h" #include "shared/QtHelpers.h" +#include "MainWindow.h" + +#ifdef Q_OS_WIN +#include +#endif static auto CONTENT_WINDOW_QML = QUrl("InteractiveWindow.qml"); @@ -87,6 +94,12 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap connect(object, SIGNAL(windowClosed()), this, SIGNAL(closed()), Qt::QueuedConnection); connect(object, SIGNAL(selfDestruct()), this, SLOT(close()), Qt::QueuedConnection); +#ifdef Q_OS_WIN + connect(object, SIGNAL(nativeWindowChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); + connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); + connect(object, SIGNAL(presentationModeChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); +#endif + QUrl sourceURL{ sourceUrl }; // If the passed URL doesn't correspond to a known scheme, assume it's a local file path if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) { @@ -279,6 +292,24 @@ int InteractiveWindow::getPresentationMode() const { return _qmlWindow->property(PRESENTATION_MODE_PROPERTY).toInt(); } +#ifdef Q_OS_WIN +void InteractiveWindow::parentNativeWindowToMainWindow() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "parentNativeWindowToMainWindow"); + return; + } + if (_qmlWindow.isNull()) { + return; + } + const auto nativeWindowProperty = _qmlWindow->property("nativeWindow"); + if (nativeWindowProperty.isNull() || !nativeWindowProperty.isValid()) { + return; + } + const auto nativeWindow = qvariant_cast(nativeWindowProperty); + SetWindowLongPtr((HWND)nativeWindow->winId(), GWLP_HWNDPARENT, (LONG)MainWindow::findMainWindow()->winId()); +} +#endif + void InteractiveWindow::setPresentationMode(int presentationMode) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setPresentationMode", Q_ARG(int, presentationMode)); diff --git a/libraries/ui/src/InteractiveWindow.h b/libraries/ui/src/InteractiveWindow.h index bf832550b5..f456b32e8d 100644 --- a/libraries/ui/src/InteractiveWindow.h +++ b/libraries/ui/src/InteractiveWindow.h @@ -84,6 +84,10 @@ private: Q_INVOKABLE void setPresentationMode(int presentationMode); Q_INVOKABLE int getPresentationMode() const; +#ifdef Q_OS_WIN + Q_INVOKABLE void parentNativeWindowToMainWindow(); +#endif + public slots: /**jsdoc diff --git a/libraries/ui/src/MainWindow.cpp b/libraries/ui/src/MainWindow.cpp index f9fc71e417..680433b2f9 100644 --- a/libraries/ui/src/MainWindow.cpp +++ b/libraries/ui/src/MainWindow.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "ui/Logging.h" @@ -39,6 +40,18 @@ MainWindow::~MainWindow() { qCDebug(uiLogging) << "Destroying main window"; } +QWindow* MainWindow::findMainWindow() { + auto windows = qApp->topLevelWindows(); + QWindow* result = nullptr; + for (const auto& window : windows) { + if (window->objectName().contains("MainWindow")) { + result = window; + break; + } + } + return result; +} + void MainWindow::restoreGeometry() { // Did not use setGeometry() on purpose, // see http://doc.qt.io/qt-5/qsettings.html#restoring-the-state-of-a-gui-application diff --git a/libraries/ui/src/MainWindow.h b/libraries/ui/src/MainWindow.h index 75421340a2..fbd48e5eb1 100644 --- a/libraries/ui/src/MainWindow.h +++ b/libraries/ui/src/MainWindow.h @@ -21,6 +21,8 @@ class MainWindow : public QMainWindow { public: explicit MainWindow(QWidget* parent = NULL); ~MainWindow(); + + static QWindow* findMainWindow(); public slots: void restoreGeometry(); diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index a5ef1457db..d82cfbbf3f 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -29,6 +29,7 @@ #include "ui/Logging.h" #include +#include "MainWindow.h" /**jsdoc * @namespace OffscreenFlags @@ -649,20 +650,7 @@ public: } private: - - static QWindow* findMainWindow() { - auto windows = qApp->topLevelWindows(); - QWindow* result = nullptr; - for (auto window : windows) { - if (window->objectName().contains("MainWindow")) { - result = window; - break; - } - } - return result; - } - - QWindow* const _mainWindow { findMainWindow() }; + QWindow* const _mainWindow { MainWindow::findMainWindow() }; QWindow* _hackWindow { nullptr }; }; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 4e920e430b..1081f8c4e7 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -140,8 +140,7 @@ int TabletButtonsProxyModel::buttonIndex(const QString &uuid) { return -1; } -void TabletButtonsProxyModel::setPageIndex(int pageIndex) -{ +void TabletButtonsProxyModel::setPageIndex(int pageIndex) { if (_pageIndex == pageIndex) return; @@ -465,6 +464,9 @@ void TabletProxy::onTabletShown() { _showRunningScripts = false; pushOntoStack("hifi/dialogs/TabletRunningScripts.qml"); } + if (_currentPathLoaded == TABLET_HOME_SOURCE_URL) { + loadHomeScreen(true); + } } } diff --git a/libraries/workload/src/workload/Space.h b/libraries/workload/src/workload/Space.h index 310955f4c6..7dcb2217f7 100644 --- a/libraries/workload/src/workload/Space.h +++ b/libraries/workload/src/workload/Space.h @@ -70,7 +70,8 @@ private: using SpacePointer = std::shared_ptr; using Changes = std::vector; using IndexVectors = std::vector; -using Timings = std::vector; +using Timing_ns = std::chrono::nanoseconds; +using Timings = std::vector; } // namespace workload diff --git a/libraries/workload/src/workload/ViewTask.cpp b/libraries/workload/src/workload/ViewTask.cpp index 5d9953cdf4..9d0c693149 100644 --- a/libraries/workload/src/workload/ViewTask.cpp +++ b/libraries/workload/src/workload/ViewTask.cpp @@ -102,9 +102,16 @@ void ControlViews::run(const workload::WorkloadContextPointer& runContext, const // Export the ranges and timings for debuging if (inTimings.size()) { - _dataExport.timings[workload::Region::R1] = std::chrono::duration(inTimings[0]).count(); + // NOTE for reference: + // inTimings[0] = prePhysics entities + // inTimings[1] = prePhysics avatars + // inTimings[2] = stepPhysics + // inTimings[3] = postPhysics + // inTimings[4] = non-physical kinematics + // inTimings[5] = game loop + _dataExport.timings[workload::Region::R1] = std::chrono::duration(inTimings[2] + inTimings[3]).count(); _dataExport.timings[workload::Region::R2] = _dataExport.timings[workload::Region::R1]; - _dataExport.timings[workload::Region::R3] = std::chrono::duration(inTimings[1]).count(); + _dataExport.timings[workload::Region::R3] = std::chrono::duration(inTimings[4]).count(); doExport = true; } @@ -115,11 +122,29 @@ void ControlViews::run(const workload::WorkloadContextPointer& runContext, const } } -glm::vec2 Regulator::run(const Timing_ns& regulationDuration, const Timing_ns& measured, const glm::vec2& current) { - // Regulate next value based on current moving toward the goal budget - float error_ms = std::chrono::duration(_budget - measured).count(); - float coef = glm::clamp(error_ms / std::chrono::duration(regulationDuration).count(), -1.0f, 1.0f); - return current * (1.0f + coef * (error_ms < 0.0f ? _relativeStepDown : _relativeStepUp)); +glm::vec2 Regulator::run(const Timing_ns& deltaTime, const Timing_ns& measuredTime, const glm::vec2& currentFrontBack) { + // measure signal: average and noise + const float FILTER_TIMESCALE = 0.5f * (float)NSECS_PER_SECOND; + float del = deltaTime.count() / FILTER_TIMESCALE; + if (del > 1.0f) { + del = 1.0f; // clamp for stability + } + _measuredTimeAverage = (1.0f - del) * _measuredTimeAverage + del * measuredTime.count(); + float diff = measuredTime.count() - _measuredTimeAverage; + _measuredTimeNoiseSquared = (1.0f - del) * _measuredTimeNoiseSquared + del * diff * diff; + float noise = sqrtf(_measuredTimeNoiseSquared); + + // check budget + float offsetFromTarget = _budget.count() - _measuredTimeAverage; + if (fabsf(offsetFromTarget) < noise) { + // budget is within the noise --> do nothing + return currentFrontBack; + } + + // compute response + glm::vec2 stepDelta = offsetFromTarget < 0.0f ? -_relativeStepDown : _relativeStepUp; + stepDelta *= glm::min(1.0f, (fabsf(offsetFromTarget) - noise) / noise); // ease out of "do nothing" + return currentFrontBack * (1.0f + stepDelta); } glm::vec2 Regulator::clamp(const glm::vec2& backFront) const { @@ -128,17 +153,24 @@ glm::vec2 Regulator::clamp(const glm::vec2& backFront) const { } void ControlViews::regulateViews(workload::Views& outViews, const workload::Timings& timings) { - for (auto& outView : outViews) { for (int32_t r = 0; r < workload::Region::NUM_VIEW_REGIONS; r++) { outView.regionBackFronts[r] = regionBackFronts[r]; } } - auto loopDuration = std::chrono::nanoseconds{ std::chrono::milliseconds(16) }; - regionBackFronts[workload::Region::R1] = regionRegulators[workload::Region::R1].run(loopDuration, timings[0], regionBackFronts[workload::Region::R1]); - regionBackFronts[workload::Region::R2] = regionRegulators[workload::Region::R2].run(loopDuration, timings[0], regionBackFronts[workload::Region::R2]); - regionBackFronts[workload::Region::R3] = regionRegulators[workload::Region::R3].run(loopDuration, timings[1], regionBackFronts[workload::Region::R3]); + // Note: for reference: + // timings[0] = prePhysics entities + // timings[1] = prePhysics avatars + // timings[2] = stepPhysics + // timings[3] = postPhysics + // timings[4] = non-physical kinematics + // timings[5] = game loop + + auto loopDuration = timings[5]; + regionBackFronts[workload::Region::R1] = regionRegulators[workload::Region::R1].run(loopDuration, timings[2] + timings[3], regionBackFronts[workload::Region::R1]); + regionBackFronts[workload::Region::R2] = regionRegulators[workload::Region::R2].run(loopDuration, timings[2] + timings[3], regionBackFronts[workload::Region::R2]); + regionBackFronts[workload::Region::R3] = regionRegulators[workload::Region::R3].run(loopDuration, timings[4], regionBackFronts[workload::Region::R3]); enforceRegionContainment(); for (auto& outView : outViews) { diff --git a/libraries/workload/src/workload/ViewTask.h b/libraries/workload/src/workload/ViewTask.h index 4fdd7e0f4c..207bc04276 100644 --- a/libraries/workload/src/workload/ViewTask.h +++ b/libraries/workload/src/workload/ViewTask.h @@ -37,8 +37,8 @@ namespace workload { { 250.0f, 16000.0f } }; - const float RELATIVE_STEP_DOWN = 0.05f; - const float RELATIVE_STEP_UP = 0.04f; + const float RELATIVE_STEP_DOWN = 0.11f; + const float RELATIVE_STEP_UP = 0.09f; class SetupViewsConfig : public Job::Config{ Q_OBJECT @@ -192,7 +192,7 @@ namespace workload { struct Data { - bool regulateViewRanges{ false }; // regulation is OFF by default + bool regulateViewRanges{ true }; // regulation is ON by default } data; struct DataExport { @@ -212,20 +212,31 @@ namespace workload { }; struct Regulator { - using Timing_ns = std::chrono::nanoseconds; - Timing_ns _budget{ std::chrono::milliseconds(2) }; glm::vec2 _minRange{ MIN_VIEW_BACK_FRONTS[0] }; glm::vec2 _maxRange{ MAX_VIEW_BACK_FRONTS[0] }; - glm::vec2 _relativeStepDown{ RELATIVE_STEP_DOWN }; glm::vec2 _relativeStepUp{ RELATIVE_STEP_UP }; - + Timing_ns _budget{ std::chrono::milliseconds(2) }; + float _measuredTimeAverage { 0.0f }; + float _measuredTimeNoiseSquared { 0.0f }; Regulator() {} - Regulator(const Timing_ns& budget_ns, const glm::vec2& minRange, const glm::vec2& maxRange, const glm::vec2& relativeStepDown, const glm::vec2& relativeStepUp) : - _budget(budget_ns), _minRange(minRange), _maxRange(maxRange), _relativeStepDown(relativeStepDown), _relativeStepUp(relativeStepUp) {} + Regulator(const Timing_ns& budget_ns, + const glm::vec2& minRange, + const glm::vec2& maxRange, + const glm::vec2& relativeStepDown, + const glm::vec2& relativeStepUp) : + _minRange(minRange), + _maxRange(maxRange), + _relativeStepDown(relativeStepDown), + _relativeStepUp(relativeStepUp), + _budget(budget_ns), + _measuredTimeAverage(budget_ns.count()), + _measuredTimeNoiseSquared(0.0f) + {} - glm::vec2 run(const Timing_ns& regulationDuration, const Timing_ns& measured, const glm::vec2& current); + void setBudget(const Timing_ns& budget) { _budget = budget; } + glm::vec2 run(const Timing_ns& deltaTime, const Timing_ns& measuredTime, const glm::vec2& currentFrontBack); glm::vec2 clamp(const glm::vec2& backFront) const; }; diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index b275660c0f..aaf5ca7260 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -38,6 +38,10 @@ var DEFAULT_SCRIPTS_SEPARATE = [ //"system/chat.js" ]; +if (Settings.getValue("enableInterstitialMode", false)) { + DEFAULT_SCRIPTS_SEPARATE.push("system/interstitialPage.js"); +} + // add a menu item for debugging var MENU_CATEGORY = "Developer"; var MENU_ITEM = "Debug defaultScripts.js"; diff --git a/scripts/developer/debugging/debugWindow.js b/scripts/developer/debugging/debugWindow.js index 9522676007..993ca49a40 100644 --- a/scripts/developer/debugging/debugWindow.js +++ b/scripts/developer/debugging/debugWindow.js @@ -23,7 +23,7 @@ if (scripts.length >= 2) { var qml = Script.resolvePath('debugWindow.qml'); var HMD_DEBUG_WINDOW_GEOMETRY_KEY = 'hmdDebugWindowGeometry'; -var hmdDebugWindowGeometryValue = Settings.getValue(HMD_DEBUG_WINDOW_GEOMETRY_KEY) +var hmdDebugWindowGeometryValue = Settings.getValue(HMD_DEBUG_WINDOW_GEOMETRY_KEY); var windowWidth = 400; var windowHeight = 900; @@ -34,12 +34,13 @@ var windowY = 0; if (hmdDebugWindowGeometryValue !== '') { var geometry = JSON.parse(hmdDebugWindowGeometryValue); - - windowWidth = geometry.width - windowHeight = geometry.height - windowX = geometry.x - windowY = geometry.y - hasPosition = true; + if ((geometry.x !== 0) && (geometry.y !== 0)) { + windowWidth = geometry.width; + windowHeight = geometry.height; + windowX = geometry.x; + windowY = geometry.y; + hasPosition = true; + } } var window = new OverlayWindow({ @@ -52,6 +53,12 @@ if (hasPosition) { window.setPosition(windowX, windowY); } +window.visibleChanged.connect(function() { + if (!window.visible) { + window.setVisible(true); + } +}); + window.closed.connect(function () { Script.stop(); }); var getFormattedDate = function() { @@ -93,10 +100,10 @@ Script.scriptEnding.connect(function () { y: window.position.y, width: window.size.x, height: window.size.y - }) + }); Settings.setValue(HMD_DEBUG_WINDOW_GEOMETRY_KEY, geometry); window.close(); -}) +}); }()); diff --git a/scripts/developer/utilities/audio/Stats.qml b/scripts/developer/utilities/audio/Stats.qml index 7f559ea664..f359e9b04c 100644 --- a/scripts/developer/utilities/audio/Stats.qml +++ b/scripts/developer/utilities/audio/Stats.qml @@ -12,12 +12,12 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "../../../../resources/qml/controls-uit" as HifiControls +import "qrc:////qml//controls-uit" as HifiControls Column { id: stats width: parent.width - property bool showGraphs: toggleGraphs.checked + property alias showGraphs: toggleGraphs.toggle Item { width: parent.width @@ -25,10 +25,10 @@ Column { HifiControls.Button { id: toggleGraphs - property bool checked: false + property bool toggle: false anchors.horizontalCenter: parent.horizontalCenter - text: checked ? "Hide graphs" : "Show graphs" - onClicked: function() { checked = !checked; } + text: toggle ? "Hide graphs" : "Show graphs" + onClicked: { toggle = !toggle;} } } @@ -40,7 +40,8 @@ Column { Section { label: "Latency" - description: "Audio pipeline latency, broken out and summed" + // description: "Audio pipeline latency, broken out and summed" + description: label control: ColumnLayout { MovingValue { label: "Input Read"; source: AudioStats.inputReadMsMax; showGraphs: stats.showGraphs } MovingValue { label: "Input Ring"; source: AudioStats.inputUnplayedMsMax; showGraphs: stats.showGraphs } @@ -62,7 +63,8 @@ Column { Section { label: "Upstream Jitter" - description: "Timegaps in packets sent to the mixer" + // description: "Timegaps in packets sent to the mixer" + description: label control: Jitter { max: AudioStats.sentTimegapMsMaxWindow avg: AudioStats.sentTimegapMsAvgWindow @@ -76,13 +78,15 @@ Column { Section { label: "Mixer (upstream)" - description: "This client's remote audio stream, as seen by the server's mixer" + // description: "This client's remote audio stream, as seen by the server's mixer" + description: label control: Stream { stream: AudioStats.mixerStream; showGraphs: stats.showGraphs } } Section { label: "Client (downstream)" - description: "This client's received audio stream, between the network and the OS" + // description: "This client's received audio stream, between the network and the OS" + description: label control: Stream { stream: AudioStats.clientStream; showGraphs: stats.showGraphs } } } diff --git a/scripts/developer/utilities/audio/TabletStats.qml b/scripts/developer/utilities/audio/TabletStats.qml index 130b90f032..2f8d212a2a 100644 --- a/scripts/developer/utilities/audio/TabletStats.qml +++ b/scripts/developer/utilities/audio/TabletStats.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "../../../../resources/qml/styles-uit" +import "qrc:////qml//styles-uit" Item { id: dialog diff --git a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml index 916c9cae55..f239cc010a 100644 --- a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml +++ b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml @@ -48,6 +48,11 @@ Item { property var valueMax : 1 + property var valueMin : 0 + + property var displayMinAt0 : true + property var _displayMaxValue : 1 + property var _displayMinValue : 0 property var _values property var tick : 0 @@ -71,7 +76,9 @@ Item { value: value, fromBinding: isBinding, valueMax: 1, + valueMin: 0, numSamplesConstantMax: 0, + numSamplesConstantMin: 0, valueHistory: new Array(), label: (plot["label"] !== undefined ? plot["label"] : ""), color: (plot["color"] !== undefined ? plot["color"] : "white"), @@ -90,6 +97,11 @@ Item { _values[i].valueMax *= 0.25 // Fast reduce the max value as we click } } + function resetMin() { + for (var i = 0; i < _values.length; i++) { + _values[i].valueMin *= 0.25 // Fast reduce the min value as we click + } + } function pullFreshValues() { // Wait until values are created to begin pulling @@ -99,6 +111,7 @@ Item { tick++; var currentValueMax = 0 + var currentValueMin = 0 for (var i = 0; i < _values.length; i++) { var currentVal = (+_values[i].object[_values[i].value]) * _values[i].scale; @@ -112,26 +125,47 @@ Item { _values[i].valueMax *= 0.99 _values[i].numSamplesConstantMax = 0 } + if (lostValue <= _values[i].valueMin) { + _values[i].valueMin *= 0.99 + _values[i].numSamplesConstantMin = 0 + } } if (_values[i].valueMax < currentVal) { _values[i].valueMax = currentVal; _values[i].numSamplesConstantMax = 0 } + if (_values[i].valueMin > currentVal) { + _values[i].valueMin = currentVal; + _values[i].numSamplesConstantMin = 0 + } if (_values[i].numSamplesConstantMax > VALUE_HISTORY_SIZE) { _values[i].numSamplesConstantMax = 0 _values[i].valueMax *= 0.95 // lower slowly the current max if no new above max since a while } - + if (_values[i].numSamplesConstantMin > VALUE_HISTORY_SIZE) { + _values[i].numSamplesConstantMin = 0 + _values[i].valueMin *= 0.95 // lower slowly the current min if no new above min since a while + } + if (currentValueMax < _values[i].valueMax) { currentValueMax = _values[i].valueMax } + if (currentValueMin > _values[i].valueMin) { + currentValueMin = _values[i].valueMin + } } if ((valueMax < currentValueMax) || (tick % VALUE_HISTORY_SIZE == 0)) { valueMax = currentValueMax; } + if ((valueMin > currentValueMin) || (tick % VALUE_HISTORY_SIZE == 0)) { + valueMin = currentValueMin; + } + _displayMaxValue = valueMax; + _displayMinValue = ( displayMinAt0 ? 0 : valueMin ) + mycanvas.requestPaint() } @@ -152,10 +186,10 @@ Item { } function pixelFromVal(val, valScale) { - return lineHeight + (height - lineHeight) * (1 - (0.9) * val / valueMax); + return lineHeight + (height - lineHeight) * (1 - (0.99) * (val - _displayMinValue) / (_displayMaxValue - _displayMinValue)); } function valueFromPixel(pixY) { - return ((pixY - lineHeight) / (height - lineHeight) - 1) * valueMax / (-0.9); + return _displayMinValue + (((pixY - lineHeight) / (height - lineHeight) - 1) * (_displayMaxValue - _displayMinValue) / (-0.99)); } function plotValueHistory(ctx, valHistory, color) { var widthStep= width / (valHistory.length - 1); @@ -183,8 +217,10 @@ Item { function displayTitle(ctx, text, maxVal) { ctx.fillStyle = "grey"; ctx.textAlign = "right"; - ctx.fillText(displayValue(valueFromPixel(lineHeight), root.valueUnit), width, lineHeight); + ctx.fillText("max " + displayValue(_displayMaxValue, root.valueUnit), width, pixelFromVal(_displayMaxValue)); + ctx.fillText("min " + displayValue(_displayMinValue, root.valueUnit), width, pixelFromVal(_displayMinValue)); + ctx.fillStyle = "white"; ctx.textAlign = "left"; ctx.fillText(text, 0, lineHeight); @@ -193,15 +229,37 @@ Item { ctx.fillStyle = Qt.rgba(0, 0, 0, root.backgroundOpacity); ctx.fillRect(0, 0, width, height); - ctx.strokeStyle= "grey"; + /* ctx.strokeStyle= "grey"; ctx.lineWidth="2"; ctx.beginPath(); ctx.moveTo(0, lineHeight + 1); - ctx.lineTo(width, lineHeight + 1); + ctx.lineTo(width, lineHeight + 1); ctx.moveTo(0, height); ctx.lineTo(width, height); + ctx.stroke();*/ + } + + function displayMaxZeroMin(ctx) { + var maxY = pixelFromVal(_displayMaxValue); + + ctx.strokeStyle= "LightSlateGray"; + ctx.lineWidth="1"; + ctx.beginPath(); + ctx.moveTo(0, maxY); + ctx.lineTo(width, maxY); ctx.stroke(); + + if (_displayMinValue != 0) { + var zeroY = pixelFromVal(0); + var minY = pixelFromVal(_displayMinValue); + ctx.beginPath(); + ctx.moveTo(0, zeroY); + ctx.lineTo(width, zeroY); + ctx.moveTo(0, minY); + ctx.lineTo(width, minY); + ctx.stroke(); + } } var ctx = getContext("2d"); @@ -215,7 +273,9 @@ Item { displayValueLegend(ctx, _values[i], i) } - displayTitle(ctx, title, valueMax) + displayMaxZeroMin(ctx); + + displayTitle(ctx, title, _displayMaxValue) } } @@ -225,6 +285,7 @@ Item { onClicked: { resetMax(); + resetMin(); } } } diff --git a/scripts/developer/utilities/render/configSlider/RichSlider.qml b/scripts/developer/utilities/render/configSlider/RichSlider.qml new file mode 100644 index 0000000000..01b14f3d48 --- /dev/null +++ b/scripts/developer/utilities/render/configSlider/RichSlider.qml @@ -0,0 +1,81 @@ +// +// RichSlider.qml +// +// Created by Zach Pomerantz on 2/8/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + + +Item { + HifiConstants { id: hifi } + id: root + + anchors.left: parent.left + anchors.right: parent.right + height: 24 + + function defaultGet() { return 0 } + function defaultSet(value) { } + + property var labelAreaWidthScale: 0.5 + property bool integral: false + property var numDigits: 2 + + property var valueVarSetter: defaultSet + property alias valueVar : sliderControl.value + + property alias min: sliderControl.minimumValue + property alias max: sliderControl.maximumValue + + property alias label: labelControl.text + property bool showLabel: true + + property bool showValue: true + + + + signal valueChanged(real value) + + Component.onCompleted: { + } + + HifiControls.Label { + id: labelControl + text: root.label + enabled: root.showLabel + anchors.left: root.left + width: root.width * root.labelAreaWidthScale + anchors.verticalCenter: root.verticalCenter + } + + HifiControls.Slider { + id: sliderControl + stepSize: root.integral ? 1.0 : 0.0 + anchors.left: labelControl.right + anchors.right: root.right + anchors.rightMargin: 0 + anchors.top: root.top + anchors.topMargin: 0 + onValueChanged: { root.valueVarSetter(value) } + } + + HifiControls.Label { + id: labelValue + enabled: root.showValue + text: sliderControl.value.toFixed(root.integral ? 0 : root.numDigits) + anchors.right: labelControl.right + anchors.rightMargin: 5 + anchors.verticalCenter: root.verticalCenter + } + +} diff --git a/scripts/developer/utilities/render/configSlider/qmldir b/scripts/developer/utilities/render/configSlider/qmldir index 6680ec9638..479f786b22 100644 --- a/scripts/developer/utilities/render/configSlider/qmldir +++ b/scripts/developer/utilities/render/configSlider/qmldir @@ -1 +1,2 @@ -ConfigSlider 1.0 ConfigSlider.qml \ No newline at end of file +ConfigSlider 1.0 ConfigSlider.qml +RichSlider 1.0 RichSlider.qml \ No newline at end of file diff --git a/scripts/developer/utilities/render/lod.js b/scripts/developer/utilities/render/lod.js index 307e509d39..f3e4208034 100644 --- a/scripts/developer/utilities/render/lod.js +++ b/scripts/developer/utilities/render/lod.js @@ -16,15 +16,9 @@ var ICON_URL = Script.resolvePath("../../../system/assets/images/lod-i.svg"); var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/lod-a.svg"); - var onScreen = false; + var onTablet = false; // set this to true to use the tablet, false use a floating window - function onClicked() { - if (onScreen) { - tablet.gotoHomeScreen(); - } else { - tablet.loadQMLSource(QMLAPP_URL); - } - } + var onAppScreen = false; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ @@ -35,6 +29,50 @@ var hasEventBridge = false; + var onScreen = false; + var window; + + function onClicked() { + if (onTablet) { + if (onAppScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(QMLAPP_URL); + } + } else { + if (onScreen) { + killWindow() + } else { + createWindow() + } + } + } + + function createWindow() { + var qml = Script.resolvePath(QMLAPP_URL); + window = Desktop.createWindow(Script.resolvePath(QMLAPP_URL), { + title: TABLET_BUTTON_NAME, + flags: Desktop.ALWAYS_ON_TOP, + presentationMode: Desktop.PresentationMode.NATIVE, + size: {x: 400, y: 600} + }); + window.closed.connect(killWindow); + window.fromQml.connect(fromQml); + onScreen = true + button.editProperties({isActive: true}); + } + + function killWindow() { + if (window !== undefined) { + window.closed.disconnect(killWindow); + window.fromQml.disconnect(fromQml); + window.close() + window = undefined + } + onScreen = false + button.editProperties({isActive: false}) + } + function wireEventBridge(on) { if (!tablet) { print("Warning in wireEventBridge(): 'tablet' undefined!"); @@ -54,23 +92,38 @@ } function onScreenChanged(type, url) { - onScreen = (url === QMLAPP_URL); - button.editProperties({isActive: onScreen}); - wireEventBridge(onScreen); - } - - function fromQml(message) { + if (onTablet) { + onAppScreen = (url === QMLAPP_URL); + + button.editProperties({isActive: onAppScreen}); + wireEventBridge(onAppScreen); + } } button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Script.scriptEnding.connect(function () { - if (onScreen) { + killWindow() + if (onAppScreen) { tablet.gotoHomeScreen(); } button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); tablet.removeButton(button); }); + + function fromQml(message) { + } + + function sendToQml(message) { + if (onTablet) { + tablet.sendToQml(message); + } else { + if (window) { + window.sendToQml(message); + } + } + } + }()); diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index d7b9f1cd57..889d8db836 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -10,20 +10,174 @@ // import QtQuick 2.5 import QtQuick.Controls 1.4 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + import "../lib/plotperf" +import "configSlider" Item { id: lodIU anchors.fill:parent + Component.onCompleted: { + Render.getConfig("RenderMainView.DrawSceneOctree").showVisibleCells = false + Render.getConfig("RenderMainView.DrawSceneOctree").showEmptyCells = false + } + + Component.onDestruction: { + Render.getConfig("RenderMainView.DrawSceneOctree").enabled = false + } + + Column { + id: topHeader + spacing: 8 + anchors.right: parent.right + anchors.left: parent.left + + HifiControls.CheckBox { + boxSize: 20 + text: "Show LOD Reticule" + checked: Render.getConfig("RenderMainView.DrawSceneOctree").enabled + onCheckedChanged: { Render.getConfig("RenderMainView.DrawSceneOctree").enabled = checked } + } + + RichSlider { + showLabel: true + showValue: false + label: "World Quality" + valueVar: LODManager["worldDetailQuality"] + valueVarSetter: (function (v) { LODManager["worldDetailQuality"] = v }) + max: 0.75 + min: 0.25 + integral: false + + anchors.left: parent.left + anchors.right: parent.right + } + + Row { + HifiControls.CheckBox { + id: autoLOD + boxSize: 20 + text: "Auto LOD" + checked: LODManager.automaticLODAdjust + onCheckedChanged: { LODManager.automaticLODAdjust = (checked) } + } + HifiControls.CheckBox { + id: showLODRegulatorDetails + visible: LODManager.automaticLODAdjust + boxSize: 20 + text: "Show LOD Details" + } + } + + RichSlider { + visible: !LODManager.automaticLODAdjust + showLabel: true + label: "LOD Angle [deg]" + valueVar: LODManager["lodAngleDeg"] + valueVarSetter: (function (v) { LODManager["lodAngleDeg"] = v }) + max: 90.0 + min: 0.5 + integral: false + + anchors.left: parent.left + anchors.right: parent.right + } + Column { + id: lodRegulatorDetails + visible: LODManager.automaticLODAdjust && showLODRegulatorDetails.checked + anchors.left: parent.left + anchors.right: parent.right + RichSlider { + visible: lodRegulatorDetails.visible + showLabel: true + label: "LOD Kp" + valueVar: LODManager["pidKp"] + valueVarSetter: (function (v) { LODManager["pidKp"] = v }) + max: 2.0 + min: 0.0 + integral: false + numDigits: 3 + + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: false && lodRegulatorDetails.visible + showLabel: true + label: "LOD Ki" + valueVar: LODManager["pidKi"] + valueVarSetter: (function (v) { LODManager["pidKi"] = v }) + max: 0.1 + min: 0.0 + integral: false + numDigits: 8 + + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: false && lodRegulatorDetails.visible + showLabel: true + label: "LOD Kd" + valueVar: LODManager["pidKd"] + valueVarSetter: (function (v) { LODManager["pidKd"] = v }) + max: 10.0 + min: 0.0 + integral: false + numDigits: 3 + + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: lodRegulatorDetails.visible + showLabel: true + label: "LOD Kv" + valueVar: LODManager["pidKv"] + valueVarSetter: (function (v) { LODManager["pidKv"] = v }) + max: 2.0 + min: 0.0 + integral: false + + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: lodRegulatorDetails.visible + showLabel: true + label: "LOD Smooth Scale" + valueVar: LODManager["smoothScale"] + valueVarSetter: (function (v) { LODManager["smoothScale"] = v }) + max: 20.0 + min: 1.0 + integral: true + + anchors.left: parent.left + anchors.right: parent.right + } + } + } + Column { id: stats - spacing: 8 - anchors.fill:parent + spacing: 4 + anchors.right: parent.right + anchors.left: parent.left + anchors.top: topHeader.bottom + anchors.bottom: parent.bottom function evalEvenHeight() { // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ? - return (height - spacing * (children.length - 1)) / children.length + var numPlots = (children.length + (lodRegulatorDetails.visible ? 1 : 0) - 2) + return (height - topLine.height - bottomLine.height - spacing * (numPlots - 1)) / (numPlots) + } + + Separator { + id: topLine } PlotPerf { @@ -38,6 +192,11 @@ Item { label: "present", color: "#FFFF00" }, + { + prop: "batchTime", + label: "batch", + color: "#00FF00" + }, { prop: "engineRunTime", label: "engineRun", @@ -58,35 +217,70 @@ Item { valueUnit: "Hz" plots: [ { - prop: "lodIncreaseFPS", - label: "LOD++", + prop: "lodTargetFPS", + label: "target", color: "#66FF66" }, { - prop: "fps", + prop: "nowRenderFPS", label: "FPS", - color: "#FFFFFF" + color: "#FFFF55" }, { - prop: "lodDecreaseFPS", - label: "LOD--", - color: "#FF6666" - } - ] - } - PlotPerf { - title: "LOD" - height: parent.evalEvenHeight() - object: LODManager - valueScale: 0.1 - valueUnit: "" - plots: [ - { - prop: "lodLevel", - label: "LOD", + prop: "smoothRenderFPS", + label: "Smooth FPS", color: "#9999FF" } ] } + PlotPerf { + title: "LOD Angle" + height: parent.evalEvenHeight() + object: LODManager + valueScale: 1.0 + valueUnit: "deg" + plots: [ + { + prop: "lodAngleDeg", + label: "LOD Angle", + color: "#9999FF" + } + ] + } + PlotPerf { + // visible: lodRegulatorDetails.visible + title: "PID Output" + height: parent.evalEvenHeight() + object: LODManager + valueScale: 1.0 + valueUnit: "deg" + valueNumDigits: 1 + displayMinAt0: false + plots: [ + { + prop: "pidOp", + label: "Op", + color: "#9999FF" + }, + { + prop: "pidOi", + label: "Oi", + color: "#FFFFFF" + }, + { + prop: "pidOd", + label: "Od", + color: "#FF6666" + }, + { + prop: "pidO", + label: "Output", + color: "#66FF66" + } + ] + } + Separator { + id: bottomLine + } } } diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index 6d2986768a..dab377911b 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -1,5 +1,5 @@ "use strict"; -/*global Tablet, Script*/ +/* global Tablet, Script */ // // libraries/appUi.js // @@ -11,6 +11,7 @@ // function AppUi(properties) { + var request = Script.require('request').request; /* Example development order: 1. var AppUi = Script.require('appUi'); 2. Put appname-i.svg, appname-a.svg in graphicsDirectory (where non-default graphicsDirectory can be added in #3). @@ -31,37 +32,63 @@ function AppUi(properties) { var that = this; function defaultButton(name, suffix) { var base = that[name] || (that.buttonPrefix + suffix); - that[name] = (base.indexOf('/') >= 0) ? base : (that.graphicsDirectory + base); //poor man's merge + that[name] = (base.indexOf('/') >= 0) ? base : (that.graphicsDirectory + base); // poor man's merge } // Defaults: that.tabletName = "com.highfidelity.interface.tablet.system"; that.inject = ""; that.graphicsDirectory = "icons/tablet-icons/"; // Where to look for button svgs. See below. + that.additionalAppScreens = []; that.checkIsOpen = function checkIsOpen(type, tabletUrl) { // Are we active? Value used to set isOpen. - return (type === that.type) && that.currentUrl && (tabletUrl.indexOf(that.currentUrl) >= 0); // Actual url may have prefix or suffix. + // Actual url may have prefix or suffix. + return (type === that.currentVisibleScreenType) && + that.currentVisibleUrl && + ((that.home.indexOf(that.currentVisibleUrl) > -1) || + (that.additionalAppScreens.indexOf(that.currentVisibleUrl) > -1)); }; - that.setCurrentData = function setCurrentData(url) { - that.currentUrl = url; - that.type = /.qml$/.test(url) ? 'QML' : 'Web'; - } - that.open = function open(optionalUrl) { // How to open the app. + that.setCurrentVisibleScreenMetadata = function setCurrentVisibleScreenMetadata(type, url) { + that.currentVisibleScreenType = type; + that.currentVisibleUrl = url; + }; + that.open = function open(optionalUrl, optionalInject) { // How to open the app. var url = optionalUrl || that.home; - that.setCurrentData(url); - if (that.isQML()) { + var inject = optionalInject || that.inject; + + if (that.isQMLUrl(url)) { that.tablet.loadQMLSource(url); } else { - that.tablet.gotoWebScreen(url, that.inject); + that.tablet.gotoWebScreen(url, inject); + } + }; + // Opens some app on top of the current app (on desktop, opens new window) + that.openNewAppOnTop = function openNewAppOnTop(url, optionalInject) { + var inject = optionalInject || ""; + if (that.isQMLUrl(url)) { + that.tablet.loadQMLOnTop(url); + } else { + that.tablet.loadWebScreenOnTop(url, inject); } }; that.close = function close() { // How to close the app. - that.currentUrl = ""; + that.currentVisibleUrl = ""; // for toolbar-mode: go back to home screen, this will close the window. that.tablet.gotoHomeScreen(); }; that.buttonActive = function buttonActive(isActive) { // How to make the button active (white). that.button.editProperties({isActive: isActive}); }; + that.isQMLUrl = function isQMLUrl(url) { + var type = /.qml$/.test(url) ? 'QML' : 'Web'; + return type === 'QML'; + }; + that.isCurrentlyOnQMLScreen = function isCurrentlyOnQMLScreen() { + return that.currentVisibleScreenType === 'QML'; + }; + + // + // START Notification Handling Defaults + // that.messagesWaiting = function messagesWaiting(isWaiting) { // How to indicate a message light on button. // Note that waitingButton doesn't have to exist unless someone explicitly calls this with isWaiting true. that.button.editProperties({ @@ -69,16 +96,124 @@ function AppUi(properties) { activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton }); }; - that.isQML = function isQML() { // We set type property in onClick. - return that.type === 'QML'; + that.notificationPollTimeout = false; + that.notificationPollTimeoutMs = 60000; + that.notificationPollEndpoint = false; + that.notificationPollStopPaginatingConditionMet = false; + that.notificationDataProcessPage = function (data) { + return data; }; - that.eventSignal = function eventSignal() { // What signal to hook onMessage to. - return that.isQML() ? that.tablet.fromQml : that.tablet.webEventReceived; + that.notificationPollCallback = that.ignore; + that.notificationPollCaresAboutSince = false; + that.notificationInitialCallbackMade = false; + that.notificationDisplayBanner = function (message) { + Window.displayAnnouncement(message); + }; + // + // END Notification Handling Defaults + // + + // Handlers + that.onScreenChanged = function onScreenChanged(type, url) { + // Set isOpen, wireEventBridge, set buttonActive as appropriate, + // and finally call onOpened() or onClosed() IFF defined. + that.setCurrentVisibleScreenMetadata(type, url); + if (that.checkIsOpen(type, url)) { + that.wireEventBridge(true); + if (!that.isOpen) { + that.buttonActive(true); + if (that.onOpened) { + that.onOpened(); + } + that.isOpen = true; + } + } else { // Not us. Should we do something for type Home, Menu, and particularly Closed (meaning tablet hidden? + that.wireEventBridge(false); + if (that.isOpen) { + that.buttonActive(false); + if (that.onClosed) { + that.onClosed(); + } + that.isOpen = false; + } + } + console.debug(that.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type + + "\nNew screen URL: " + url + "\nCurrent app open status: " + that.isOpen + "\n"); }; // Overwrite with the given properties: Object.keys(properties).forEach(function (key) { that[key] = properties[key]; }); + // + // START Notification Handling + // + var METAVERSE_BASE = Account.metaverseServerURL; + var currentDataPageToRetrieve = 1; + var concatenatedServerResponse = new Array(); + that.notificationPoll = function () { + if (!that.notificationPollEndpoint) { + return; + } + + // User is "appearing offline" + if (GlobalServices.findableBy === "none") { + that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs); + return; + } + + var url = METAVERSE_BASE + that.notificationPollEndpoint; + + if (that.notificationPollCaresAboutSince) { + url = url + "&since=" + (new Date().getTime()); + } + + console.debug(that.buttonName, 'polling for notifications at endpoint', url); + + function requestCallback(error, response) { + if (error || (response.status !== 'success')) { + print("Error: unable to get", url, error || response.status); + that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs); + return; + } + + if (!that.notificationPollStopPaginatingConditionMet || that.notificationPollStopPaginatingConditionMet(response)) { + that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs); + + var notificationData; + if (concatenatedServerResponse.length) { + notificationData = concatenatedServerResponse; + } else { + notificationData = that.notificationDataProcessPage(response); + } + console.debug(that.buttonName, 'notification data for processing:', JSON.stringify(notificationData)); + that.notificationPollCallback(notificationData); + that.notificationInitialCallbackMade = true; + currentDataPageToRetrieve = 1; + concatenatedServerResponse = new Array(); + } else { + concatenatedServerResponse = concatenatedServerResponse.concat(that.notificationDataProcessPage(response)); + currentDataPageToRetrieve++; + request({ uri: (url + "&page=" + currentDataPageToRetrieve) }, requestCallback); + } + } + + request({ uri: url }, requestCallback); + }; + + // This won't do anything if there isn't a notification endpoint set + that.notificationPoll(); + + function availabilityChanged() { + if (that.notificationPollTimeout) { + Script.clearTimeout(that.notificationPollTimeout); + that.notificationPollTimeout = false; + } + that.notificationPoll(); + } + // + // END Notification Handling + // + // Properties: that.tablet = Tablet.getTablet(that.tabletName); // Must be after we gather properties. @@ -100,65 +235,58 @@ function AppUi(properties) { } that.button = that.tablet.addButton(buttonOptions); that.ignore = function ignore() { }; - - // Handlers - that.onScreenChanged = function onScreenChanged(type, url) { - // Set isOpen, wireEventBridge, set buttonActive as appropriate, - // and finally call onOpened() or onClosed() IFF defined. - console.debug(that.buttonName, 'onScreenChanged', type, url, that.isOpen); - if (that.checkIsOpen(type, url)) { - if (!that.isOpen) { - that.wireEventBridge(true); - that.buttonActive(true); - if (that.onOpened) { - that.onOpened(); - } - that.isOpen = true; - } - - } else { // Not us. Should we do something for type Home, Menu, and particularly Closed (meaning tablet hidden? - if (that.isOpen) { - that.wireEventBridge(false); - that.buttonActive(false); - if (that.onClosed) { - that.onClosed(); - } - that.isOpen = false; - } - } - }; - that.hasEventBridge = false; + that.hasOutboundEventBridge = false; + that.hasInboundQmlEventBridge = false; + that.hasInboundHtmlEventBridge = false; // HTML event bridge uses strings, not objects. Here we abstract over that. // (Although injected javascript still has to use JSON.stringify/JSON.parse.) - that.sendToHtml = function (messageObject) { that.tablet.emitScriptEvent(JSON.stringify(messageObject)); }; - that.fromHtml = function (messageString) { that.onMessage(JSON.parse(messageString)); }; + that.sendToHtml = function (messageObject) { + that.tablet.emitScriptEvent(JSON.stringify(messageObject)); + }; + that.fromHtml = function (messageString) { + var parsedMessage = JSON.parse(messageString); + parsedMessage.messageSrc = "HTML"; + that.onMessage(parsedMessage); + }; that.sendMessage = that.ignore; that.wireEventBridge = function wireEventBridge(on) { // Uniquivocally sets that.sendMessage(messageObject) to do the right thing. - // Sets hasEventBridge and wires onMessage to eventSignal as appropriate, IFF onMessage defined. - var handler, isQml = that.isQML(); + // Sets has*EventBridge and wires onMessage to the proper event bridge as appropriate, IFF onMessage defined. + var isCurrentlyOnQMLScreen = that.isCurrentlyOnQMLScreen(); // Outbound (always, regardless of whether there is an inbound handler). if (on) { - that.sendMessage = isQml ? that.tablet.sendToQml : that.sendToHtml; + that.sendMessage = isCurrentlyOnQMLScreen ? that.tablet.sendToQml : that.sendToHtml; + that.hasOutboundEventBridge = true; } else { that.sendMessage = that.ignore; + that.hasOutboundEventBridge = false; } - if (!that.onMessage) { return; } + if (!that.onMessage) { + return; + } // Inbound - handler = isQml ? that.onMessage : that.fromHtml; if (on) { - if (!that.hasEventBridge) { - console.debug(that.buttonName, 'connecting', that.eventSignal()); - that.eventSignal().connect(handler); - that.hasEventBridge = true; + if (isCurrentlyOnQMLScreen && !that.hasInboundQmlEventBridge) { + console.debug(that.buttonName, 'connecting', that.tablet.fromQml); + that.tablet.fromQml.connect(that.onMessage); + that.hasInboundQmlEventBridge = true; + } else if (!isCurrentlyOnQMLScreen && !that.hasInboundHtmlEventBridge) { + console.debug(that.buttonName, 'connecting', that.tablet.webEventReceived); + that.tablet.webEventReceived.connect(that.fromHtml); + that.hasInboundHtmlEventBridge = true; } } else { - if (that.hasEventBridge) { - console.debug(that.buttonName, 'disconnecting', that.eventSignal()); - that.eventSignal().disconnect(handler); - that.hasEventBridge = false; + if (that.hasInboundQmlEventBridge) { + console.debug(that.buttonName, 'disconnecting', that.tablet.fromQml); + that.tablet.fromQml.disconnect(that.onMessage); + that.hasInboundQmlEventBridge = false; + } + if (that.hasInboundHtmlEventBridge) { + console.debug(that.buttonName, 'disconnecting', that.tablet.webEventReceived); + that.tablet.webEventReceived.disconnect(that.fromHtml); + that.hasInboundHtmlEventBridge = false; } } }; @@ -175,6 +303,7 @@ function AppUi(properties) { } : that.ignore; that.onScriptEnding = function onScriptEnding() { // Close if necessary, clean up any remaining handlers, and remove the button. + GlobalServices.findableByChanged.disconnect(availabilityChanged); if (that.isOpen) { that.close(); } @@ -185,10 +314,15 @@ function AppUi(properties) { } that.tablet.removeButton(that.button); } + if (that.notificationPollTimeout) { + Script.clearInterval(that.notificationPollTimeout); + that.notificationPollTimeout = false; + } }; // Set up the handlers. that.tablet.screenChanged.connect(that.onScreenChanged); that.button.clicked.connect(that.onClicked); Script.scriptEnding.connect(that.onScriptEnding); + GlobalServices.findableByChanged.connect(availabilityChanged); } module.exports = AppUi; diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 8730273e7c..993ea30c2e 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -1,5 +1,5 @@ "use strict"; -/*jslint vars:true, plusplus:true, forin:true*/ +/* jslint vars:true, plusplus:true, forin:true */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // wallet.js @@ -14,627 +14,529 @@ /* global getConnectionData */ (function () { // BEGIN LOCAL_SCOPE - Script.include("/~/system/libraries/accountUtils.js"); - Script.include("/~/system/libraries/connectionUtils.js"); +Script.include("/~/system/libraries/accountUtils.js"); +Script.include("/~/system/libraries/connectionUtils.js"); +var AppUi = Script.require('appUi'); - var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; +var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; - // BEGIN AVATAR SELECTOR LOGIC - var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; - var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; - var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; +// BEGIN AVATAR SELECTOR LOGIC +var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; +var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; +var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; - var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. +var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. - function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. - overlays[key] = this; - this.key = key; - this.selected = false; - this.hovering = false; - this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... +function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. + overlays[key] = this; + this.key = key; + this.selected = false; + this.hovering = false; + this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... +} +// Instance methods: +ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay + Overlays.deleteOverlay(this.activeOverlay); + delete overlays[this.key]; +}; + +ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay + Overlays.editOverlay(this.activeOverlay, properties); +}; + +function color(selected, hovering) { + var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; + function scale(component) { + var delta = 0xFF - component; + return component; } - // Instance methods: - ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay - Overlays.deleteOverlay(this.activeOverlay); - delete overlays[this.key]; - }; - - ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay - Overlays.editOverlay(this.activeOverlay, properties); - }; - - function color(selected, hovering) { - var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; - function scale(component) { - var delta = 0xFF - component; - return component; - } - return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; - } - // so we don't have to traverse the overlays to get the last one - var lastHoveringId = 0; - ExtendedOverlay.prototype.hover = function (hovering) { - this.hovering = hovering; - if (this.key === lastHoveringId) { - if (hovering) { - return; - } - lastHoveringId = 0; - } - this.editOverlay({ color: color(this.selected, hovering) }); + return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; +} +// so we don't have to traverse the overlays to get the last one +var lastHoveringId = 0; +ExtendedOverlay.prototype.hover = function (hovering) { + this.hovering = hovering; + if (this.key === lastHoveringId) { if (hovering) { - // un-hover the last hovering overlay - if (lastHoveringId && lastHoveringId !== this.key) { - ExtendedOverlay.get(lastHoveringId).hover(false); - } - lastHoveringId = this.key; - } - }; - ExtendedOverlay.prototype.select = function (selected) { - if (this.selected === selected) { return; } - - this.editOverlay({ color: color(selected, this.hovering) }); - this.selected = selected; - }; - // Class methods: - var selectedId = false; - ExtendedOverlay.isSelected = function (id) { - return selectedId === id; - }; - ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier - return overlays[key]; - }; - ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. - var key; - for (key in overlays) { - if (iterator(ExtendedOverlay.get(key))) { - return; - } - } - }; - ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) - if (lastHoveringId) { + lastHoveringId = 0; + } + this.editOverlay({ color: color(this.selected, hovering) }); + if (hovering) { + // un-hover the last hovering overlay + if (lastHoveringId && lastHoveringId !== this.key) { ExtendedOverlay.get(lastHoveringId).hover(false); } - }; + lastHoveringId = this.key; + } +}; +ExtendedOverlay.prototype.select = function (selected) { + if (this.selected === selected) { + return; + } - // hit(overlay) on the one overlay intersected by pickRay, if any. - // noHit() if no ExtendedOverlay was intersected (helps with hover) - ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { - var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones. - if (!pickedOverlay.intersects) { - if (noHit) { - return noHit(); - } + this.editOverlay({ color: color(selected, this.hovering) }); + this.selected = selected; +}; +// Class methods: +var selectedId = false; +ExtendedOverlay.isSelected = function (id) { + return selectedId === id; +}; +ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier + return overlays[key]; +}; +ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. + var key; + for (key in overlays) { + if (iterator(ExtendedOverlay.get(key))) { return; } - ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. - if ((overlay.activeOverlay) === pickedOverlay.overlayID) { - hit(overlay); - return true; - } - }); - }; - - function addAvatarNode(id) { - return new ExtendedOverlay(id, "sphere", { - drawInFront: true, - solid: true, - alpha: 0.8, - color: color(false, false), - ignoreRayIntersection: false - }); } - - var pingPong = true; - function updateOverlays() { - var eye = Camera.position; - AvatarList.getAvatarIdentifiers().forEach(function (id) { - if (!id) { - return; // don't update ourself, or avatars we're not interested in - } - var avatar = AvatarList.getAvatar(id); - if (!avatar) { - return; // will be deleted below if there had been an overlay. - } - var overlay = ExtendedOverlay.get(id); - if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. - overlay = addAvatarNode(id); - } - var target = avatar.position; - var distance = Vec3.distance(target, eye); - var offset = 0.2; - var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position) - var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can - if (headIndex > 0) { - offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; - } - - // move a bit in front, towards the camera - target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); - - // now bump it up a bit - target.y = target.y + offset; - - overlay.ping = pingPong; - overlay.editOverlay({ - color: color(ExtendedOverlay.isSelected(id), overlay.hovering), - position: target, - dimensions: 0.032 * distance - }); - }); - pingPong = !pingPong; - ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) - if (overlay.ping === pingPong) { - overlay.deleteOverlay(); - } - }); - } - function removeOverlays() { - selectedId = false; - lastHoveringId = 0; - ExtendedOverlay.some(function (overlay) { - overlay.deleteOverlay(); - }); +}; +ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) + if (lastHoveringId) { + ExtendedOverlay.get(lastHoveringId).hover(false); } +}; - // - // Clicks. - // - function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { - if (selectedId === id) { - var message = { - method: 'updateSelectedRecipientUsername', - userName: username === "" ? "unknown username" : username - }; - sendToQml(message); +// hit(overlay) on the one overlay intersected by pickRay, if any. +// noHit() if no ExtendedOverlay was intersected (helps with hover) +ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { + var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones. + if (!pickedOverlay.intersects) { + if (noHit) { + return noHit(); } + return; } - function handleClick(pickRay) { - ExtendedOverlay.applyPickRay(pickRay, function (overlay) { - var nextSelectedStatus = !overlay.selected; - var avatarId = overlay.key; - selectedId = nextSelectedStatus ? avatarId : false; - if (nextSelectedStatus) { - Users.requestUsernameFromID(avatarId); - } - var message = { - method: 'selectRecipient', - id: avatarId, - isSelected: nextSelectedStatus, - displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', - userName: '' - }; - sendToQml(message); - - ExtendedOverlay.some(function (overlay) { - var id = overlay.key; - var selected = ExtendedOverlay.isSelected(id); - overlay.select(selected); - }); - + ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. + if ((overlay.activeOverlay) === pickedOverlay.overlayID) { + hit(overlay); return true; - }); - } - function handleMouseEvent(mousePressEvent) { // handleClick if we get one. - if (!mousePressEvent.isLeftButton) { - return; } - handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); + }); +}; + +function addAvatarNode(id) { + return new ExtendedOverlay(id, "sphere", { + drawInFront: true, + solid: true, + alpha: 0.8, + color: color(false, false), + ignoreRayIntersection: false + }); +} + +var pingPong = true; +function updateOverlays() { + var eye = Camera.position; + AvatarList.getAvatarIdentifiers().forEach(function (id) { + if (!id) { + return; // don't update ourself, or avatars we're not interested in + } + var avatar = AvatarList.getAvatar(id); + if (!avatar) { + return; // will be deleted below if there had been an overlay. + } + var overlay = ExtendedOverlay.get(id); + if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. + overlay = addAvatarNode(id); + } + var target = avatar.position; + var distance = Vec3.distance(target, eye); + var offset = 0.2; + var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position) + var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can + if (headIndex > 0) { + offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; + } + + // move a bit in front, towards the camera + target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); + + // now bump it up a bit + target.y = target.y + offset; + + overlay.ping = pingPong; + overlay.editOverlay({ + color: color(ExtendedOverlay.isSelected(id), overlay.hovering), + position: target, + dimensions: 0.032 * distance + }); + }); + pingPong = !pingPong; + ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) + if (overlay.ping === pingPong) { + overlay.deleteOverlay(); + } + }); +} +function removeOverlays() { + selectedId = false; + lastHoveringId = 0; + ExtendedOverlay.some(function (overlay) { + overlay.deleteOverlay(); + }); +} + +// +// Clicks. +// +function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { + if (selectedId === id) { + var message = { + method: 'updateSelectedRecipientUsername', + userName: username === "" ? "unknown username" : username + }; + ui.sendMessage(message); } - function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic - ExtendedOverlay.applyPickRay(pickRay, function (overlay) { - overlay.hover(true); - }, function () { +} +function handleClick(pickRay) { + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + var nextSelectedStatus = !overlay.selected; + var avatarId = overlay.key; + selectedId = nextSelectedStatus ? avatarId : false; + if (nextSelectedStatus) { + Users.requestUsernameFromID(avatarId); + } + var message = { + method: 'selectRecipient', + id: avatarId, + isSelected: nextSelectedStatus, + displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', + userName: '' + }; + ui.sendMessage(message); + + ExtendedOverlay.some(function (overlay) { + var id = overlay.key; + var selected = ExtendedOverlay.isSelected(id); + overlay.select(selected); + }); + + return true; + }); +} +function handleMouseEvent(mousePressEvent) { // handleClick if we get one. + if (!mousePressEvent.isLeftButton) { + return; + } + handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); +} +function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + overlay.hover(true); + }, function () { + ExtendedOverlay.unHover(); + }); +} + +// handy global to keep track of which hand is the mouse (if any) +var currentHandPressed = 0; +var TRIGGER_CLICK_THRESHOLD = 0.85; +var TRIGGER_PRESS_THRESHOLD = 0.05; + +function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position + var pickRay; + if (HMD.active) { + if (currentHandPressed !== 0) { + pickRay = controllerComputePickRay(currentHandPressed); + } else { + // nothing should hover, so ExtendedOverlay.unHover(); + return; + } + } else { + pickRay = Camera.computePickRay(event.x, event.y); + } + handleMouseMove(pickRay); +} +function handleTriggerPressed(hand, value) { + // The idea is if you press one trigger, it is the one + // we will consider the mouse. Even if the other is pressed, + // we ignore it until this one is no longer pressed. + var isPressed = value > TRIGGER_PRESS_THRESHOLD; + if (currentHandPressed === 0) { + currentHandPressed = isPressed ? hand : 0; + return; + } + if (currentHandPressed === hand) { + currentHandPressed = isPressed ? hand : 0; + return; + } + // otherwise, the other hand is still triggered + // so do nothing. +} + +// We get mouseMoveEvents from the handControllers, via handControllerPointer. +// But we don't get mousePressEvents. +var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); +var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); +function controllerComputePickRay(hand) { + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { + return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; + } +} +function makeClickHandler(hand) { + return function (clicked) { + if (clicked > TRIGGER_CLICK_THRESHOLD) { + var pickRay = controllerComputePickRay(hand); + handleClick(pickRay); + } + }; +} +function makePressHandler(hand) { + return function (value) { + handleTriggerPressed(hand, value); + }; +} +triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); +triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); +triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); +triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); +// END AVATAR SELECTOR LOGIC + +var sendMoneyRecipient; +var sendMoneyParticleEffectUpdateTimer; +var particleEffectTimestamp; +var sendMoneyParticleEffect; +var SEND_MONEY_PARTICLE_TIMER_UPDATE = 250; +var SEND_MONEY_PARTICLE_EMITTING_DURATION = 3000; +var SEND_MONEY_PARTICLE_LIFETIME_SECONDS = 8; +var SEND_MONEY_PARTICLE_PROPERTIES = { + accelerationSpread: { x: 0, y: 0, z: 0 }, + alpha: 1, + alphaFinish: 1, + alphaSpread: 0, + alphaStart: 1, + azimuthFinish: 0, + azimuthStart: -6, + color: { red: 143, green: 5, blue: 255 }, + colorFinish: { red: 255, green: 0, blue: 204 }, + colorSpread: { red: 0, green: 0, blue: 0 }, + colorStart: { red: 0, green: 136, blue: 255 }, + emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0 }, + emitRate: 4, + emitSpeed: 2.1, + emitterShouldTrail: true, + isEmitting: 1, + lifespan: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate + lifetime: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, + maxParticles: 20, + name: 'hfc-particles', + particleRadius: 0.2, + polarFinish: 0, + polarStart: 0, + radiusFinish: 0.05, + radiusSpread: 0, + radiusStart: 0.2, + speedSpread: 0, + textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png", + type: 'ParticleEffect' +}; + +var MS_PER_SEC = 1000; +function updateSendMoneyParticleEffect() { + var timestampNow = Date.now(); + if ((timestampNow - particleEffectTimestamp) > (SEND_MONEY_PARTICLE_LIFETIME_SECONDS * MS_PER_SEC)) { + deleteSendMoneyParticleEffect(); + return; + } else if ((timestampNow - particleEffectTimestamp) > SEND_MONEY_PARTICLE_EMITTING_DURATION) { + Entities.editEntity(sendMoneyParticleEffect, { + isEmitting: 0 + }); + } else if (sendMoneyParticleEffect) { + var recipientPosition = AvatarList.getAvatar(sendMoneyRecipient).position; + var distance = Vec3.distance(recipientPosition, MyAvatar.position); + var accel = Vec3.subtract(recipientPosition, MyAvatar.position); + accel.y -= 3.0; + var life = Math.sqrt(2 * distance / Vec3.length(accel)); + Entities.editEntity(sendMoneyParticleEffect, { + emitAcceleration: accel, + lifespan: life }); } +} - // handy global to keep track of which hand is the mouse (if any) - var currentHandPressed = 0; - var TRIGGER_CLICK_THRESHOLD = 0.85; - var TRIGGER_PRESS_THRESHOLD = 0.05; +function deleteSendMoneyParticleEffect() { + if (sendMoneyParticleEffectUpdateTimer) { + Script.clearInterval(sendMoneyParticleEffectUpdateTimer); + sendMoneyParticleEffectUpdateTimer = null; + } + if (sendMoneyParticleEffect) { + sendMoneyParticleEffect = Entities.deleteEntity(sendMoneyParticleEffect); + } + sendMoneyRecipient = null; +} - function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position - var pickRay; - if (HMD.active) { - if (currentHandPressed !== 0) { - pickRay = controllerComputePickRay(currentHandPressed); - } else { - // nothing should hover, so - ExtendedOverlay.unHover(); - return; - } - } else { - pickRay = Camera.computePickRay(event.x, event.y); - } - handleMouseMove(pickRay); - } - function handleTriggerPressed(hand, value) { - // The idea is if you press one trigger, it is the one - // we will consider the mouse. Even if the other is pressed, - // we ignore it until this one is no longer pressed. - var isPressed = value > TRIGGER_PRESS_THRESHOLD; - if (currentHandPressed === 0) { - currentHandPressed = isPressed ? hand : 0; - return; - } - if (currentHandPressed === hand) { - currentHandPressed = isPressed ? hand : 0; - return; - } - // otherwise, the other hand is still triggered - // so do nothing. - } - - // We get mouseMoveEvents from the handControllers, via handControllerPointer. - // But we don't get mousePressEvents. - var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); - var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); - function controllerComputePickRay(hand) { - var controllerPose = getControllerWorldLocation(hand, true); - if (controllerPose.valid) { - return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; - } - } - function makeClickHandler(hand) { - return function (clicked) { - if (clicked > TRIGGER_CLICK_THRESHOLD) { - var pickRay = controllerComputePickRay(hand); - handleClick(pickRay); - } - }; - } - function makePressHandler(hand) { - return function (value) { - handleTriggerPressed(hand, value); - }; - } - triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); - triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); - triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); - triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); - // END AVATAR SELECTOR LOGIC - - // Function Name: onButtonClicked() - // - // Description: - // -Fired when the app button is pressed. - // - // Relevant Variables: - // -WALLET_QML_SOURCE: The path to the Wallet QML - // -onWalletScreen: true/false depending on whether we're looking at the app. - var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; - var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; - var onWalletScreen = false; - function onButtonClicked() { - if (!tablet) { - print("Warning in buttonClicked(): 'tablet' undefined!"); - return; - } - if (onWalletScreen) { - // for toolbar-mode: go back to home screen, this will close the window. - tablet.gotoHomeScreen(); - } else { - tablet.loadQMLSource(WALLET_QML_SOURCE); - } - } - - // Function Name: sendToQml() - // - // Description: - // -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to - // the QML in the format "{method, params}", like json-rpc. See also fromQml(). - function sendToQml(message) { - tablet.sendToQml(message); - } - - var sendMoneyRecipient; - var sendMoneyParticleEffectUpdateTimer; - var particleEffectTimestamp; - var sendMoneyParticleEffect; - var SEND_MONEY_PARTICLE_TIMER_UPDATE = 250; - var SEND_MONEY_PARTICLE_EMITTING_DURATION = 3000; - var SEND_MONEY_PARTICLE_LIFETIME_SECONDS = 8; - var SEND_MONEY_PARTICLE_PROPERTIES = { - accelerationSpread: { x: 0, y: 0, z: 0 }, - alpha: 1, - alphaFinish: 1, - alphaSpread: 0, - alphaStart: 1, - azimuthFinish: 0, - azimuthStart: -6, - color: { red: 143, green: 5, blue: 255 }, - colorFinish: { red: 255, green: 0, blue: 204 }, - colorSpread: { red: 0, green: 0, blue: 0 }, - colorStart: { red: 0, green: 136, blue: 255 }, - emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate - emitDimensions: { x: 0, y: 0, z: 0 }, - emitOrientation: { x: 0, y: 0, z: 0 }, - emitRate: 4, - emitSpeed: 2.1, - emitterShouldTrail: true, - isEmitting: 1, - lifespan: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate - lifetime: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, - maxParticles: 20, - name: 'hfc-particles', - particleRadius: 0.2, - polarFinish: 0, - polarStart: 0, - radiusFinish: 0.05, - radiusSpread: 0, - radiusStart: 0.2, - speedSpread: 0, - textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png", - type: 'ParticleEffect' - }; - - function updateSendMoneyParticleEffect() { - var timestampNow = Date.now(); - if ((timestampNow - particleEffectTimestamp) > (SEND_MONEY_PARTICLE_LIFETIME_SECONDS * 1000)) { - deleteSendMoneyParticleEffect(); - return; - } else if ((timestampNow - particleEffectTimestamp) > SEND_MONEY_PARTICLE_EMITTING_DURATION) { - Entities.editEntity(sendMoneyParticleEffect, { - isEmitting: 0 - }); - } else if (sendMoneyParticleEffect) { - var recipientPosition = AvatarList.getAvatar(sendMoneyRecipient).position; - var distance = Vec3.distance(recipientPosition, MyAvatar.position); - var accel = Vec3.subtract(recipientPosition, MyAvatar.position); - accel.y -= 3.0; - var life = Math.sqrt(2 * distance / Vec3.length(accel)); - Entities.editEntity(sendMoneyParticleEffect, { - emitAcceleration: accel, - lifespan: life - }); - } - } - - function deleteSendMoneyParticleEffect() { - if (sendMoneyParticleEffectUpdateTimer) { - Script.clearInterval(sendMoneyParticleEffectUpdateTimer); - sendMoneyParticleEffectUpdateTimer = null; - } - if (sendMoneyParticleEffect) { - sendMoneyParticleEffect = Entities.deleteEntity(sendMoneyParticleEffect); - } - sendMoneyRecipient = null; - } - - function onUsernameChanged() { +function onUsernameChanged() { + if (Account.username !== Settings.getValue("wallet/savedUsername")) { Settings.setValue("wallet/autoLogout", false); Settings.setValue("wallet/savedUsername", ""); } +} - // Function Name: fromQml() - // - // Description: - // -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML - // in the format "{method, params}", like json-rpc. See also sendToQml(). - var isHmdPreviewDisabled = true; - var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); - - function fromQml(message) { - switch (message.method) { - case 'passphrasePopup_cancelClicked': - case 'needsLogIn_cancelClicked': - tablet.gotoHomeScreen(); - break; - case 'walletSetup_cancelClicked': - switch (message.referrer) { - case '': // User clicked "Wallet" app - case undefined: - case null: - tablet.gotoHomeScreen(); - break; - case 'purchases': - case 'marketplace cta': - case 'mainPage': - tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - default: // User needs to return to an individual marketplace item URL - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL); - break; - } - break; - case 'needsLogIn_loginClicked': - openLoginWindow(); - break; - case 'disableHmdPreview': - break; // do nothing here, handled in marketplaces.js - case 'maybeEnableHmdPreview': - break; // do nothing here, handled in marketplaces.js - case 'passphraseReset': - onButtonClicked(); - onButtonClicked(); - break; - case 'walletReset': - Settings.setValue("isFirstUseOfPurchases", true); - onButtonClicked(); - onButtonClicked(); - break; - case 'transactionHistory_linkClicked': - tablet.gotoWebScreen(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'goToPurchases_fromWalletHome': - case 'goToPurchases': - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); - break; - case 'goToMarketplaceMainPage': - tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'goToMarketplaceItemPage': - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'refreshConnections': - print('Refreshing Connections...'); - getConnectionData(false); - break; - case 'enable_ChooseRecipientNearbyMode': - if (!isUpdateOverlaysWired) { - Script.update.connect(updateOverlays); - isUpdateOverlaysWired = true; - } - break; - case 'disable_ChooseRecipientNearbyMode': - if (isUpdateOverlaysWired) { - Script.update.disconnect(updateOverlays); - isUpdateOverlaysWired = false; - } - removeOverlays(); - break; - case 'sendAsset_sendPublicly': - if (message.assetName === "") { - deleteSendMoneyParticleEffect(); - sendMoneyRecipient = message.recipient; - var amount = message.amount; - var props = SEND_MONEY_PARTICLE_PROPERTIES; - props.parentID = MyAvatar.sessionUUID; - props.position = MyAvatar.position; - props.position.y += 0.2; - if (message.effectImage) { - props.textures = message.effectImage; - } - sendMoneyParticleEffect = Entities.addEntity(props, true); - particleEffectTimestamp = Date.now(); - updateSendMoneyParticleEffect(); - sendMoneyParticleEffectUpdateTimer = Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE); - } - break; - case 'transactionHistory_goToBank': - if (Account.metaverseServerURL.indexOf("staging") >= 0) { - Window.location = "hifi://hifiqa-master-metaverse-staging"; // So that we can test in staging. - } else { - Window.location = "hifi://BankOfHighFidelity"; - } - break; - case 'wallet_availableUpdatesReceived': - // NOP - break; - case 'http.request': - // Handled elsewhere, don't log. - break; - default: - print('Unrecognized message from QML:', JSON.stringify(message)); +// Function Name: fromQml() +// +// Description: +// -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML +// in the format "{method, params}", like json-rpc. See also sendToQml(). +var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; +var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); +function fromQml(message) { + switch (message.method) { + case 'passphrasePopup_cancelClicked': + case 'needsLogIn_cancelClicked': + ui.close(); + break; + case 'walletSetup_cancelClicked': + switch (message.referrer) { + case '': // User clicked "Wallet" app + case undefined: + case null: + ui.close(); + break; + case 'purchases': + case 'marketplace cta': + case 'mainPage': + ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + default: // User needs to return to an individual marketplace item URL + ui.open(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL); + break; } - } - - // Function Name: wireEventBridge() - // - // Description: - // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or - // disable to event bridge. - // - // Relevant Variables: - // -hasEventBridge: true/false depending on whether we've already connected the event bridge. - var hasEventBridge = false; - function wireEventBridge(on) { - if (!tablet) { - print("Warning in wireEventBridge(): 'tablet' undefined!"); - return; - } - if (on) { - if (!hasEventBridge) { - tablet.fromQml.connect(fromQml); - hasEventBridge = true; - } - } else { - if (hasEventBridge) { - tablet.fromQml.disconnect(fromQml); - hasEventBridge = false; - } - } - } - - // Function Name: onTabletScreenChanged() - // - // Description: - // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string - // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. - function onTabletScreenChanged(type, url) { - onWalletScreen = (type === "QML" && url === WALLET_QML_SOURCE); - wireEventBridge(onWalletScreen); - // Change button to active when window is first openend, false otherwise. - if (button) { - button.editProperties({ isActive: onWalletScreen }); - } - - if (onWalletScreen) { - if (!isWired) { - Users.usernameFromIDReply.connect(usernameFromIDReply); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - triggerMapping.enable(); - triggerPressMapping.enable(); - } - isWired = true; - } else { - off(); - } - } - - // - // Manage the connection between the button and the window. - // - var button; - var buttonName = "WALLET"; - var tablet = null; - var walletEnabled = Settings.getValue("commerce", true); - function startup() { - GlobalServices.myUsernameChanged.connect(onUsernameChanged); - if (walletEnabled) { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - button = tablet.addButton({ - text: buttonName, - icon: "icons/tablet-icons/wallet-i.svg", - activeIcon: "icons/tablet-icons/wallet-a.svg", - sortOrder: 10 - }); - button.clicked.connect(onButtonClicked); - tablet.screenChanged.connect(onTabletScreenChanged); - } - } - var isWired = false; - var isUpdateOverlaysWired = false; - function off() { - if (isWired) { - Users.usernameFromIDReply.disconnect(usernameFromIDReply); - Controller.mousePressEvent.disconnect(handleMouseEvent); - Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); - triggerMapping.disable(); - triggerPressMapping.disable(); - - isWired = false; + break; + case 'needsLogIn_loginClicked': + openLoginWindow(); + break; + case 'disableHmdPreview': + break; // do nothing here, handled in marketplaces.js + case 'maybeEnableHmdPreview': + break; // do nothing here, handled in marketplaces.js + case 'transactionHistory_linkClicked': + ui.open(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'goToPurchases_fromWalletHome': + case 'goToPurchases': + ui.open(MARKETPLACE_PURCHASES_QML_PATH); + break; + case 'goToMarketplaceMainPage': + ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'goToMarketplaceItemPage': + ui.open(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'refreshConnections': + print('Refreshing Connections...'); + getConnectionData(false); + break; + case 'enable_ChooseRecipientNearbyMode': + if (!isUpdateOverlaysWired) { + Script.update.connect(updateOverlays); + isUpdateOverlaysWired = true; } + break; + case 'disable_ChooseRecipientNearbyMode': if (isUpdateOverlaysWired) { Script.update.disconnect(updateOverlays); isUpdateOverlaysWired = false; } removeOverlays(); - } - function shutdown() { - GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); - button.clicked.disconnect(onButtonClicked); - tablet.removeButton(button); - deleteSendMoneyParticleEffect(); - if (tablet) { - tablet.screenChanged.disconnect(onTabletScreenChanged); - if (onWalletScreen) { - tablet.gotoHomeScreen(); + break; + case 'sendAsset_sendPublicly': + if (message.assetName === "") { + deleteSendMoneyParticleEffect(); + sendMoneyRecipient = message.recipient; + var amount = message.amount; + var props = SEND_MONEY_PARTICLE_PROPERTIES; + props.parentID = MyAvatar.sessionUUID; + props.position = MyAvatar.position; + props.position.y += 0.2; + if (message.effectImage) { + props.textures = message.effectImage; } + sendMoneyParticleEffect = Entities.addEntity(props, true); + particleEffectTimestamp = Date.now(); + updateSendMoneyParticleEffect(); + sendMoneyParticleEffectUpdateTimer = Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE); } - off(); + break; + case 'transactionHistory_goToBank': + if (Account.metaverseServerURL.indexOf("staging") >= 0) { + Window.location = "hifi://hifiqa-master-metaverse-staging"; // So that we can test in staging. + } else { + Window.location = "hifi://BankOfHighFidelity"; + } + break; + case 'wallet_availableUpdatesReceived': + // NOP + break; + case 'http.request': + // Handled elsewhere, don't log. + break; + default: + print('Unrecognized message from QML:', JSON.stringify(message)); } +} - // - // Run the functions. - // - startup(); - Script.scriptEnding.connect(shutdown); +function walletOpened() { + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); +} +function walletClosed() { + off(); +} + +// +// Manage the connection between the button and the window. +// +var BUTTON_NAME = "WALLET"; +var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; +var ui; +function startup() { + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 10, + home: WALLET_QML_SOURCE, + onOpened: walletOpened, + onClosed: walletClosed, + onMessage: fromQml + }); + GlobalServices.myUsernameChanged.connect(onUsernameChanged); +} +var isUpdateOverlaysWired = false; +function off() { + Users.usernameFromIDReply.disconnect(usernameFromIDReply); + Controller.mousePressEvent.disconnect(handleMouseEvent); + Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); + triggerMapping.disable(); + triggerPressMapping.disable(); + + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); +} +function shutdown() { + GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); + deleteSendMoneyParticleEffect(); + off(); +} + +// +// Run the functions. +// +startup(); +Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index e4563fda14..5e798ed680 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -14,7 +14,8 @@ PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic, getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI - Picks, makeLaserLockInfo Xform, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST + Picks, makeLaserLockInfo Xform, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST, + worldPositionToRegistrationFrameMatrix */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -593,18 +594,9 @@ Script.include("/~/system/libraries/Xform.js"); this.calculateOffset = function(controllerData) { if (this.distanceHolding || this.distanceRotating) { - var targetProps = Entities.getEntityProperties(this.targetObject.entityID, [ - "position", - "rotation" - ]); - var zeroVector = { x: 0, y: 0, z:0, w: 0 }; - var intersection = controllerData.rayPicks[this.hand].intersection; - var intersectionMat = new Xform(zeroVector, intersection); - var modelMat = new Xform(targetProps.rotation, targetProps.position); - var modelMatInv = modelMat.inv(); - var xformMat = Xform.mul(modelMatInv, intersectionMat); - var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos); - return offsetMat; + var targetProps = Entities.getEntityProperties(this.targetObject.entityID, + [ "position", "rotation", "registrationPoint", "dimensions" ]); + return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); } return undefined; }; diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js index a080e75325..78abcb9b20 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js @@ -12,7 +12,7 @@ makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Picks, makeLaserLockInfo, Xform, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST, - Uuid + Uuid, worldPositionToRegistrationFrameMatrix */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -572,18 +572,9 @@ Script.include("/~/system/libraries/Xform.js"); this.calculateOffset = function(controllerData) { if (this.distanceHolding || this.distanceRotating) { - var targetProps = Entities.getEntityProperties(this.targetObject.entityID, [ - "position", - "rotation" - ]); - var zeroVector = { x: 0, y: 0, z:0, w: 0 }; - var intersection = controllerData.rayPicks[this.hand].intersection; - var intersectionMat = new Xform(zeroVector, intersection); - var modelMat = new Xform(targetProps.rotation, targetProps.position); - var modelMatInv = modelMat.inv(); - var xformMat = Xform.mul(modelMatInv, intersectionMat); - var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos); - return offsetMat; + var targetProps = Entities.getEntityProperties(this.targetObject.entityID, + [ "position", "rotation", "registrationPoint", "dimensions" ]); + return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); } return undefined; }; diff --git a/scripts/system/controllers/controllerModules/farParentGrabEntity.js b/scripts/system/controllers/controllerModules/farParentGrabEntity.js index 439b5e5f51..a9ec246a32 100644 --- a/scripts/system/controllers/controllerModules/farParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farParentGrabEntity.js @@ -11,7 +11,8 @@ Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Picks, makeLaserLockInfo, Xform, makeLaserParams, AddressManager, - getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, Uuid, findGroupParent + getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, Uuid, findGroupParent, + worldPositionToRegistrationFrameMatrix */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -615,18 +616,9 @@ Script.include("/~/system/libraries/Xform.js"); this.calculateOffset = function(controllerData) { if (this.distanceHolding || this.distanceRotating) { - var targetProps = Entities.getEntityProperties(this.targetObject.entityID, [ - "position", - "rotation" - ]); - var zeroVector = { x: 0, y: 0, z:0, w: 0 }; - var intersection = controllerData.rayPicks[this.hand].intersection; - var intersectionMat = new Xform(zeroVector, intersection); - var modelMat = new Xform(targetProps.rotation, targetProps.position); - var modelMatInv = modelMat.inv(); - var xformMat = Xform.mul(modelMatInv, intersectionMat); - var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos); - return offsetMat; + var targetProps = Entities.getEntityProperties(this.targetObject.entityID, + [ "position", "rotation", "registrationPoint", "dimensions" ]); + return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); } return undefined; }; diff --git a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js index bc09ebee7a..3a33082f64 100644 --- a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js +++ b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js @@ -37,7 +37,7 @@ this.highlightedEntities = []; this.parameters = dispatcherUtils.makeDispatcherModuleParameters( - 480, + 120, this.hand === dispatcherUtils.RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index d590545532..2bdd89f141 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -29,7 +29,7 @@ Script.include("/~/system/libraries/utils.js"); this.reticleMaxY; this.parameters = makeDispatcherModuleParameters( - 160, + 200, this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], [], 100, diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js index 7b78d5e1c4..02863cf935 100644 --- a/scripts/system/controllers/controllerModules/inVREditMode.js +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -21,7 +21,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.disableModules = false; var NO_HAND_LASER = -1; // Invalid hand parameter so that default laser is not displayed. this.parameters = makeDispatcherModuleParameters( - 200, // Not too high otherwise the tablet laser doesn't work. + 240, // Not too high otherwise the tablet laser doesn't work. this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index a8de76aebd..27c1b458b8 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -26,7 +26,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.hapticTargetID = null; this.parameters = makeDispatcherModuleParameters( - 500, + 140, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js index 962ae89bb9..366fcd3032 100644 --- a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js +++ b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js @@ -21,7 +21,7 @@ this.hyperlink = ""; this.parameters = makeDispatcherModuleParameters( - 485, + 125, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 035c150a5d..f805dbf60e 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -24,6 +24,9 @@ Script.include("/~/system/libraries/controllers.js"); // XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true; // XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC; + // this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378 + var GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }; // x = upward, y = forward, z = lateral + function getGrabOffset(handController) { var offset = GRAB_POINT_SPHERE_OFFSET; if (handController === Controller.Standard.LeftHand) { @@ -54,7 +57,7 @@ Script.include("/~/system/libraries/controllers.js"); this.cloneAllowed = true; this.parameters = makeDispatcherModuleParameters( - 500, + 140, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index 6a9cd9fbcd..f1126dedc3 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -29,7 +29,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.startSent = false; this.parameters = makeDispatcherModuleParameters( - 480, + 120, this.hand === RIGHT_HAND ? ["rightHandTrigger", "rightHand"] : ["leftHandTrigger", "leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index e4dd1c43fa..deaa934f99 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -62,40 +62,51 @@ Script.include("/~/system/libraries/controllers.js"); alpha: 1, width: 0.025 }; + var teleportPath = { color: COLORS_TELEPORT_CAN_TELEPORT, alpha: 1, width: 0.025 }; + var seatPath = { color: COLORS_TELEPORT_SEAT, alpha: 1, width: 0.025 }; + var teleportEnd = { type: "model", url: TARGET_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, ignorePickIntersection: true }; + var seatEnd = { type: "model", url: SEAT_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, ignorePickIntersection: true }; - - + + var collisionEnd = { + type: "shape", + shape: "box", + dimensions: { x: 1.0, y: 0.001, z: 1.0 }, + alpha: 0.0, + ignorePickIntersection: true + }; + var teleportRenderStates = [{name: "cancel", path: cancelPath}, {name: "teleport", path: teleportPath, end: teleportEnd}, - {name: "seat", path: seatPath, end: seatEnd}]; + {name: "seat", path: seatPath, end: seatEnd}, + {name: "collision", end: collisionEnd}]; var DEFAULT_DISTANCE = 8.0; var teleportDefaultRenderStates = [{name: "cancel", distance: DEFAULT_DISTANCE, path: cancelPath}]; var ignoredEntities = []; - var TELEPORTER_STATES = { IDLE: 'idle', TARGETTING: 'targetting', @@ -104,8 +115,9 @@ Script.include("/~/system/libraries/controllers.js"); var TARGET = { NONE: 'none', // Not currently targetting anything - INVISIBLE: 'invisible', // The current target is an invvsible surface INVALID: 'invalid', // The current target is invalid (wall, ceiling, etc.) + COLLIDES: 'collides', // Insufficient space to accommodate the avatar capsule + DISCREPANCY: 'discrepancy', // We are not 100% sure the avatar will fit so we trigger safe landing SURFACE: 'surface', // The current target is a valid surface SEAT: 'seat' // The current target is a seat }; @@ -115,6 +127,7 @@ Script.include("/~/system/libraries/controllers.js"); function Teleporter(hand) { var _this = this; + this.init = false; this.hand = hand; this.buttonValue = 0; this.disabled = false; // used by the 'Hifi-Teleport-Disabler' message handler @@ -122,74 +135,138 @@ Script.include("/~/system/libraries/controllers.js"); this.state = TELEPORTER_STATES.IDLE; this.currentTarget = TARGET.INVALID; this.currentResult = null; + this.capsuleThreshold = 0.05; + this.pickHeightOffset = 0.05; this.getOtherModule = function() { var otherModule = this.hand === RIGHT_HAND ? leftTeleporter : rightTeleporter; return otherModule; }; - this.teleportParabolaHandVisible = Pointers.createPointer(PickType.Parabola, { - joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", - dirOffset: { x: 0, y: 1, z: 0.1 }, - posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 }, - filter: Picks.PICK_ENTITIES, - faceAvatar: true, - scaleWithAvatar: true, - centerEndY: false, - speed: speed, - accelerationAxis: accelerationAxis, - rotateAccelerationWithAvatar: true, - renderStates: teleportRenderStates, - defaultRenderStates: teleportDefaultRenderStates, - maxDistance: 8.0 - }); - this.teleportParabolaHandInvisible = Pointers.createPointer(PickType.Parabola, { - joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", - dirOffset: { x: 0, y: 1, z: 0.1 }, - posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 }, - filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, - faceAvatar: true, - scaleWithAvatar: true, - centerEndY: false, - speed: speed, - accelerationAxis: accelerationAxis, - rotateAccelerationWithAvatar: true, - renderStates: teleportRenderStates, - maxDistance: 8.0 - }); - this.teleportParabolaHeadVisible = Pointers.createPointer(PickType.Parabola, { - joint: "Avatar", - filter: Picks.PICK_ENTITIES, - faceAvatar: true, - scaleWithAvatar: true, - centerEndY: false, - speed: speed, - accelerationAxis: accelerationAxis, - rotateAccelerationWithAvatar: true, - renderStates: teleportRenderStates, - defaultRenderStates: teleportDefaultRenderStates, - maxDistance: 8.0 - }); - this.teleportParabolaHeadInvisible = Pointers.createPointer(PickType.Parabola, { - joint: "Avatar", - filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, - faceAvatar: true, - scaleWithAvatar: true, - centerEndY: false, - speed: speed, - accelerationAxis: accelerationAxis, - rotateAccelerationWithAvatar: true, - renderStates: teleportRenderStates, - maxDistance: 8.0 - }); + this.teleportHeadCollisionPick; + this.teleportHandCollisionPick; + this.teleportParabolaHandVisuals; + this.teleportParabolaHandCollisions; + this.teleportParabolaHeadVisuals; + this.teleportParabolaHeadCollisions; this.cleanup = function() { - Pointers.removePointer(this.teleportParabolaHandVisible); - Pointers.removePointer(this.teleportParabolaHandInvisible); - Pointers.removePointer(this.teleportParabolaHeadVisible); - Pointers.removePointer(this.teleportParabolaHeadInvisible); + Pointers.removePointer(_this.teleportParabolaHandVisuals); + Pointers.removePointer(_this.teleportParabolaHandCollisions); + Pointers.removePointer(_this.teleportParabolaHeadVisuals); + Pointers.removePointer(_this.teleportParabolaHeadCollisions); + Picks.removePick(_this.teleportHandCollisionPick); + Picks.removePick(_this.teleportHeadCollisionPick); }; + this.initPointers = function () { + if (_this.init) { + _this.cleanup(); + } + _this.teleportParabolaHandVisuals = Pointers.createPointer(PickType.Parabola, { + joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + dirOffset: { x: 0, y: 1, z: 0.1 }, + posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 }, + filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + scaleWithAvatar: true, + centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, + renderStates: teleportRenderStates, + defaultRenderStates: teleportDefaultRenderStates, + maxDistance: 8.0 + }); + + _this.teleportParabolaHandCollisions = Pointers.createPointer(PickType.Parabola, { + joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + dirOffset: { x: 0, y: 1, z: 0.1 }, + posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 }, + filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + scaleWithAvatar: true, + centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, + renderStates: teleportRenderStates, + maxDistance: 8.0 + }); + + _this.teleportParabolaHeadVisuals = Pointers.createPointer(PickType.Parabola, { + joint: "Avatar", + filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + scaleWithAvatar: true, + centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, + renderStates: teleportRenderStates, + defaultRenderStates: teleportDefaultRenderStates, + maxDistance: 8.0 + }); + + _this.teleportParabolaHeadCollisions = Pointers.createPointer(PickType.Parabola, { + joint: "Avatar", + filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + scaleWithAvatar: true, + centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, + renderStates: teleportRenderStates, + maxDistance: 8.0 + }); + + + var capsuleData = MyAvatar.getCollisionCapsule(); + + var sensorToWorldScale = MyAvatar.getSensorToWorldScale(); + + var radius = capsuleData.radius / sensorToWorldScale; + var height = (Vec3.distance(capsuleData.start, capsuleData.end) + (capsuleData.radius * 2.0)) / sensorToWorldScale; + var capsuleRatio = 10.0 * radius / height; + var offset = _this.pickHeightOffset * capsuleRatio; + + _this.teleportHandCollisionPick = Picks.createPick(PickType.Collision, { + enabled: true, + parentID: Pointers.getPointerProperties(_this.teleportParabolaHandCollisions).renderStates["collision"].end, + filter: Picks.PICK_ENTITIES | Picks.PICK_AVATARS, + shape: { + shapeType: "capsule-y", + dimensions: { + x: radius * 2.0, + y: height - (radius * 2.0), + z: radius * 2.0 + } + }, + position: { x: 0, y: offset + height * 0.5, z: 0 }, + threshold: _this.capsuleThreshold + }); + + _this.teleportHeadCollisionPick = Picks.createPick(PickType.Collision, { + enabled: true, + parentID: Pointers.getPointerProperties(_this.teleportParabolaHeadCollisions).renderStates["collision"].end, + filter: Picks.PICK_ENTITIES | Picks.PICK_AVATARS, + shape: { + shapeType: "capsule-y", + dimensions: { + x: radius * 2.0, + y: height - (radius * 2.0), + z: radius * 2.0 + } + }, + position: { x: 0, y: offset + height * 0.5, z: 0 }, + threshold: _this.capsuleThreshold + }); + _this.init = true; + } + + _this.initPointers(); + this.axisButtonStateX = 0; // Left/right axis button pressed. this.axisButtonStateY = 0; // Up/down axis button pressed. this.BUTTON_TRANSITION_DELAY = 100; // Allow time for transition from direction buttons to touch-pad. @@ -254,52 +331,56 @@ Script.include("/~/system/libraries/controllers.js"); var pose = Controller.getPoseValue(handInfo[(_this.hand === RIGHT_HAND) ? 'right' : 'left'].controllerInput); var mode = pose.valid ? _this.hand : 'head'; if (!pose.valid) { - Pointers.disablePointer(_this.teleportParabolaHandVisible); - Pointers.disablePointer(_this.teleportParabolaHandInvisible); - Pointers.enablePointer(_this.teleportParabolaHeadVisible); - Pointers.enablePointer(_this.teleportParabolaHeadInvisible); + Pointers.disablePointer(_this.teleportParabolaHandVisuals); + Pointers.disablePointer(_this.teleportParabolaHandCollisions); + Picks.disablePick(_this.teleportHandCollisionPick); + Pointers.enablePointer(_this.teleportParabolaHeadVisuals); + Pointers.enablePointer(_this.teleportParabolaHeadCollisions); + Picks.enablePick(_this.teleportHeadCollisionPick); } else { - Pointers.enablePointer(_this.teleportParabolaHandVisible); - Pointers.enablePointer(_this.teleportParabolaHandInvisible); - Pointers.disablePointer(_this.teleportParabolaHeadVisible); - Pointers.disablePointer(_this.teleportParabolaHeadInvisible); + Pointers.enablePointer(_this.teleportParabolaHandVisuals); + Pointers.enablePointer(_this.teleportParabolaHandCollisions); + Picks.enablePick(_this.teleportHandCollisionPick); + Pointers.disablePointer(_this.teleportParabolaHeadVisuals); + Pointers.disablePointer(_this.teleportParabolaHeadCollisions); + Picks.disablePick(_this.teleportHeadCollisionPick); } // We do up to 2 picks to find a teleport location. // There are 2 types of teleport locations we are interested in: - // 1. A visible floor. This can be any entity surface that points within some degree of "up" + // + // 1. A visible floor. This can be any entity surface that points within some degree of "up" + // and where the avatar capsule can be positioned without colliding + // // 2. A seat. The seat can be visible or invisible. // - // * In the first pass we pick against visible and invisible entities so that we can find invisible seats. - // We might hit an invisible entity that is not a seat, so we need to do a second pass. - // * In the second pass we pick against visible entities only. + // The Collision Pick is currently parented to the end overlay on teleportParabolaXXXXCollisions // - var result; + // TODO + // Parent the collision Pick directly to the teleportParabolaXXXXVisuals and get rid of teleportParabolaXXXXCollisions + // + var result, collisionResult; if (mode === 'head') { - result = Pointers.getPrevPickResult(_this.teleportParabolaHeadInvisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHeadCollisions); + collisionResult = Picks.getPrevPickResult(_this.teleportHeadCollisionPick); } else { - result = Pointers.getPrevPickResult(_this.teleportParabolaHandInvisible); - } - - var teleportLocationType = getTeleportTargetType(result); - if (teleportLocationType === TARGET.INVISIBLE) { - if (mode === 'head') { - result = Pointers.getPrevPickResult(_this.teleportParabolaHeadVisible); - } else { - result = Pointers.getPrevPickResult(_this.teleportParabolaHandVisible); - } - teleportLocationType = getTeleportTargetType(result); + result = Pointers.getPrevPickResult(_this.teleportParabolaHandCollisions); + collisionResult = Picks.getPrevPickResult(_this.teleportHandCollisionPick); } + + var teleportLocationType = getTeleportTargetType(result, collisionResult); if (teleportLocationType === TARGET.NONE) { // Use the cancel default state this.setTeleportState(mode, "cancel", ""); - } else if (teleportLocationType === TARGET.INVALID || teleportLocationType === TARGET.INVISIBLE) { + } else if (teleportLocationType === TARGET.INVALID) { this.setTeleportState(mode, "", "cancel"); - } else if (teleportLocationType === TARGET.SURFACE) { - this.setTeleportState(mode, "teleport", ""); + } else if (teleportLocationType === TARGET.COLLIDES) { + this.setTeleportState(mode, "cancel", "collision"); + } else if (teleportLocationType === TARGET.SURFACE || teleportLocationType === TARGET.DISCREPANCY) { + this.setTeleportState(mode, "teleport", "collision"); } else if (teleportLocationType === TARGET.SEAT) { - this.setTeleportState(mode, "", "seat"); + this.setTeleportState(mode, "collision", "seat"); } return this.teleport(result, teleportLocationType); }; @@ -314,10 +395,11 @@ Script.include("/~/system/libraries/controllers.js"); // Do nothing } else if (target === TARGET.SEAT) { Entities.callEntityMethod(result.objectID, 'sit'); - } else if (target === TARGET.SURFACE) { + } else if (target === TARGET.SURFACE || target === TARGET.DISCREPANCY) { var offset = getAvatarFootOffset(); result.intersection.y += offset; - MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false); + var shouldLandSafe = target === TARGET.DISCREPANCY; + MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false, shouldLandSafe); HMD.centerUI(); MyAvatar.centerBody(); } @@ -328,33 +410,38 @@ Script.include("/~/system/libraries/controllers.js"); }; this.disableLasers = function() { - Pointers.disablePointer(_this.teleportParabolaHandVisible); - Pointers.disablePointer(_this.teleportParabolaHandInvisible); - Pointers.disablePointer(_this.teleportParabolaHeadVisible); - Pointers.disablePointer(_this.teleportParabolaHeadInvisible); + Pointers.disablePointer(_this.teleportParabolaHandVisuals); + Pointers.disablePointer(_this.teleportParabolaHandCollisions); + Pointers.disablePointer(_this.teleportParabolaHeadVisuals); + Pointers.disablePointer(_this.teleportParabolaHeadCollisions); + Picks.disablePick(_this.teleportHeadCollisionPick); + Picks.disablePick(_this.teleportHandCollisionPick); }; this.setTeleportState = function(mode, visibleState, invisibleState) { if (mode === 'head') { - Pointers.setRenderState(_this.teleportParabolaHeadVisible, visibleState); - Pointers.setRenderState(_this.teleportParabolaHeadInvisible, invisibleState); + Pointers.setRenderState(_this.teleportParabolaHeadVisuals, visibleState); + Pointers.setRenderState(_this.teleportParabolaHeadCollisions, invisibleState); } else { - Pointers.setRenderState(_this.teleportParabolaHandVisible, visibleState); - Pointers.setRenderState(_this.teleportParabolaHandInvisible, invisibleState); + Pointers.setRenderState(_this.teleportParabolaHandVisuals, visibleState); + Pointers.setRenderState(_this.teleportParabolaHandCollisions, invisibleState); } }; this.setIgnoreEntities = function(entitiesToIgnore) { - Pointers.setIgnoreItems(this.teleportParabolaHandVisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportParabolaHandInvisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportParabolaHeadVisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportParabolaHeadInvisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHandVisuals, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHandCollisions, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHeadVisuals, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHeadCollisions, entitiesToIgnore); + Picks.setIgnoreItems(_this.teleportHeadCollisionPick, entitiesToIgnore); + Picks.setIgnoreItems(_this.teleportHandCollisionPick, entitiesToIgnore); }; } // related to repositioning the avatar after you teleport var FOOT_JOINT_NAMES = ["RightToe_End", "RightToeBase", "RightFoot"]; var DEFAULT_ROOT_TO_FOOT_OFFSET = 0.5; + function getAvatarFootOffset() { // find a valid foot jointIndex @@ -395,7 +482,29 @@ Script.include("/~/system/libraries/controllers.js"); // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from your avatar's up, then // you can't teleport there. var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; - function getTeleportTargetType(result) { + var MAX_DISCREPANCY_DISTANCE = 1.0; + var MAX_DOT_SIGN = -0.6; + + function checkForMeshDiscrepancy(result, collisionResult) { + var intersectingObjects = collisionResult.intersectingObjects; + if (intersectingObjects.length > 0 && intersectingObjects.length < 3) { + for (var j = 0; j < collisionResult.intersectingObjects.length; j++) { + var intersectingObject = collisionResult.intersectingObjects[j]; + for (var i = 0; i < intersectingObject.collisionContacts.length; i++) { + var normal = intersectingObject.collisionContacts[i].normalOnPick; + var distanceToPick = Vec3.distance(intersectingObject.collisionContacts[i].pointOnPick, result.intersection); + var normalSign = Vec3.dot(normal, Quat.getUp(MyAvatar.orientation)); + if ((distanceToPick > MAX_DISCREPANCY_DISTANCE) || (normalSign > MAX_DOT_SIGN)) { + return false; + } + } + } + return true; + } + return false; + } + + function getTeleportTargetType(result, collisionResult) { if (result.type === Picks.INTERSECTED_NONE) { return TARGET.NONE; } @@ -410,9 +519,14 @@ Script.include("/~/system/libraries/controllers.js"); return TARGET.INVALID; } } - - if (!props.visible) { - return TARGET.INVISIBLE; + var isDiscrepancy = false; + if (collisionResult.collisionRegion != undefined) { + if (collisionResult.intersects) { + isDiscrepancy = checkForMeshDiscrepancy(result, collisionResult); + if (!isDiscrepancy) { + return TARGET.COLLIDES; + } + } } var surfaceNormal = result.surfaceNormal; @@ -420,6 +534,8 @@ Script.include("/~/system/libraries/controllers.js"); if (angle > MAX_ANGLE_FROM_UP_TO_TELEPORT) { return TARGET.INVALID; + } else if (isDiscrepancy) { + return TARGET.DISCREPANCY; } else { return TARGET.SURFACE; } @@ -513,7 +629,14 @@ Script.include("/~/system/libraries/controllers.js"); } } }; - + + MyAvatar.onLoadComplete.connect(function () { + Script.setTimeout(function () { + leftTeleporter.initPointers(); + rightTeleporter.initPointers(); + }, 500); + }); + Messages.subscribe('Hifi-Teleport-Disabler'); Messages.subscribe('Hifi-Teleport-Ignore-Add'); Messages.subscribe('Hifi-Teleport-Ignore-Remove'); diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index a2fe0bfcd4..4e36355621 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -22,14 +22,14 @@ Script.include("/~/system/libraries/controllers.js"); this.running = false; this.parameters = makeDispatcherModuleParameters( - 120, + 160, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100, makeLaserParams(hand, true)); this.grabModuleWantsNearbyOverlay = function(controllerData) { - if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { var nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; var nearGrabModule = getEnabledModuleByName(nearGrabName); if (nearGrabModule) { @@ -42,6 +42,23 @@ Script.include("/~/system/libraries/controllers.js"); return true; } } + nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity"; + nearGrabModule = getEnabledModuleByName(nearGrabName); + if (nearGrabModule && nearGrabModule.isReady(controllerData)) { + // check for if near parent module is active. + var isNearGrabModuleActive = nearGrabModule.isReady(controllerData).active; + if (isNearGrabModuleActive) { + // if true, return true. + return isNearGrabModuleActive; + } else { + // check near action grab entity as a second pass. + nearGrabName = this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity"; + nearGrabModule = getEnabledModuleByName(nearGrabName); + if (nearGrabModule && nearGrabModule.isReady(controllerData)) { + return nearGrabModule.isReady(controllerData).active; + } + } + } } return false; }; @@ -50,14 +67,14 @@ Script.include("/~/system/libraries/controllers.js"); return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput; }; - this.isPointingAtTriggerable = function(controllerData, triggerPressed) { + this.isPointingAtTriggerable = function(controllerData, triggerPressed, checkEntitiesOnly) { // allow pointing at tablet, unlocked web entities, or web overlays automatically without pressing trigger, // but for pointing at locked web entities or non-web overlays user must be pressing trigger var intersection = controllerData.rayPicks[this.hand]; - if (intersection.type === Picks.INTERSECTED_OVERLAY) { - var objectID = intersection.objectID; + var objectID = intersection.objectID; + if (intersection.type === Picks.INTERSECTED_OVERLAY && !checkEntitiesOnly) { if ((HMD.tabletID && objectID === HMD.tabletID) || - (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || + (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || (HMD.homeButtonID && objectID === HMD.homeButtonID)) { return true; } else { @@ -65,9 +82,9 @@ Script.include("/~/system/libraries/controllers.js"); return overlayType === "web3d" || triggerPressed; } } else if (intersection.type === Picks.INTERSECTED_ENTITY) { - var entityProperty = Entities.getEntityProperties(intersection.objectID); - var entityType = entityProperty.type; - var isLocked = entityProperty.locked; + var entityProperties = Entities.getEntityProperties(objectID); + var entityType = entityProperties.type; + var isLocked = entityProperties.locked; return entityType === "Web" && (!isLocked || triggerPressed); } return false; @@ -103,7 +120,8 @@ Script.include("/~/system/libraries/controllers.js"); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; var allowThisModule = !otherModuleRunning || isTriggerPressed; - if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) { + + if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. @@ -121,13 +139,27 @@ Script.include("/~/system/libraries/controllers.js"); otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand. var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData); + // only allow for non-near grab var allowThisModule = !otherModuleRunning && !grabModuleNeedsToRun; var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; - if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed))) { - this.running = true; - return makeRunningValues(true, [], []); + if (allowThisModule) { + if (isTriggerPressed && !this.isPointingAtTriggerable(controllerData, isTriggerPressed, true)) { + // if trigger is down + not pointing at a web entity, keep running web surface laser + this.running = true; + return makeRunningValues(true, [], []); + } else if (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) { + // if trigger is down + pointing at a web entity/overlay, keep running web surface laser + this.running = true; + return makeRunningValues(true, [], []); + } else { + this.deleteContextOverlay(); + this.running = false; + this.dominantHandOverride = false; + return makeRunningValues(false, [], []); + } } + // if module needs to stop from near grabs or other modules are running, stop it. this.deleteContextOverlay(); this.running = false; this.dominantHandOverride = false; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index adee7c6236..b1f01e1ea9 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -20,6 +20,8 @@ var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; +var CONTROLLER_MAPPING_NAME = "com.highfidelity.editMode"; + Script.include([ "libraries/stringHelpers.js", "libraries/dataViewHelpers.js", @@ -858,8 +860,9 @@ var toolBar = (function () { propertiesTool.setVisible(false); selectionManager.clearSelections(); cameraManager.disable(); - selectionDisplay.triggerMapping.disable(); + selectionDisplay.disableTriggerMapping(); tablet.landscape = false; + Controller.disableMapping(CONTROLLER_MAPPING_NAME); } else { if (shouldUseEditTabletApp()) { tablet.loadQMLSource("hifi/tablet/Edit.qml", true); @@ -873,9 +876,10 @@ var toolBar = (function () { gridTool.setVisible(true); grid.setEnabled(true); propertiesTool.setVisible(true); - selectionDisplay.triggerMapping.enable(); + selectionDisplay.enableTriggerMapping(); print("starting tablet in landscape mode"); tablet.landscape = true; + Controller.enableMapping(CONTROLLER_MAPPING_NAME); // Not sure what the following was meant to accomplish, but it currently causes // everybody else to think that Interface has lost focus overall. fogbugzid:558 // Window.setFocus(); @@ -1735,7 +1739,6 @@ function getPositionToCreateEntity(extra) { position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); } else { position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); - position.y += 0.5; } if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { @@ -1859,30 +1862,7 @@ var keyReleaseEvent = function (event) { cameraManager.keyReleaseEvent(event); } // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items - if (event.text === "DELETE") { - deleteSelectedEntities(); - } else if (event.text === 'd' && event.isControl) { - selectionManager.clearSelections(); - } else if (event.text === "t") { - selectionDisplay.toggleSpaceMode(); - } else if (event.text === "f") { - if (isActive) { - if (selectionManager.hasSelection()) { - cameraManager.enable(); - cameraManager.focus(selectionManager.worldPosition, - selectionManager.worldDimensions, - Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); - } - } - } else if (event.text === '[') { - if (isActive) { - cameraManager.enable(); - } - } else if (event.text === 'g') { - if (isActive && selectionManager.hasSelection()) { - grid.moveToSelection(); - } - } else if (event.key === KEY_P && event.isControl && !event.isAutoRepeat ) { + if (event.key === KEY_P && event.isControl && !event.isAutoRepeat) { if (event.isShifted) { unparentSelectedEntities(); } else { @@ -1893,6 +1873,45 @@ var keyReleaseEvent = function (event) { Controller.keyReleaseEvent.connect(keyReleaseEvent); Controller.keyPressEvent.connect(keyPressEvent); +function deleteKey(value) { + if (value === 0) { // on release + deleteSelectedEntities(); + } +} +function deselectKey(value) { + if (value === 0) { // on release + selectionManager.clearSelections(); + } +} +function toggleKey(value) { + if (value === 0) { // on release + selectionDisplay.toggleSpaceMode(); + } +} +function focusKey(value) { + if (value === 0) { // on release + cameraManager.enable(); + if (selectionManager.hasSelection()) { + cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } + } +} +function gridKey(value) { + if (value === 0) { // on release + if (selectionManager.hasSelection()) { + grid.moveToSelection(); + } + } +} +var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); +mapping.from([Controller.Hardware.Keyboard.Delete]).when([!Controller.Hardware.Application.PlatformMac]).to(deleteKey); +mapping.from([Controller.Hardware.Keyboard.Backspace]).when([Controller.Hardware.Application.PlatformMac]).to(deleteKey); +mapping.from([Controller.Hardware.Keyboard.D]).when([Controller.Hardware.Keyboard.Control]).to(deselectKey); +mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); +mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); +mapping.from([Controller.Hardware.Keyboard.G]).to(gridKey); + function recursiveAdd(newParentID, parentData) { if (parentData.children !== undefined) { var children = parentData.children; @@ -2115,9 +2134,7 @@ var PropertiesTool = function (opts) { var onWebEventReceived = function(data) { try { data = JSON.parse(data); - } - catch(e) { - print('Edit.js received web event that was not valid json.'); + } catch(e) { return; } var i, properties, dY, diff, newPosition; diff --git a/scripts/system/help.js b/scripts/system/help.js index aaeb82721c..40bbf6dbe2 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -1,5 +1,5 @@ "use strict"; - +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // help.js // scripts/system/ @@ -12,50 +12,18 @@ // /* globals Tablet, Script, HMD, Controller, Menu */ -(function() { // BEGIN LOCAL_SCOPE - - var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; - var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html"; - var buttonName = "HELP"; - var onHelpScreen = false; - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - var button = tablet.addButton({ - icon: "icons/tablet-icons/help-i.svg", - activeIcon: "icons/tablet-icons/help-a.svg", - text: buttonName, - sortOrder: 6 +(function () { // BEGIN LOCAL_SCOPE +var AppUi = Script.require('appUi'); + +var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html"; +var HELP_BUTTON_NAME = "HELP"; +var ui; +function startup() { + ui = new AppUi({ + buttonName: HELP_BUTTON_NAME, + sortOrder: 6, + home: HELP_URL }); - - var enabled = false; - function onClicked() { - if (onHelpScreen) { - tablet.gotoHomeScreen(); - } else { - if (HMD.tabletID) { - Entities.editEntity(HMD.tabletID, {textures: JSON.stringify({"tex.close" : HOME_BUTTON_TEXTURE})}); - } - Menu.triggerOption('Help...'); - onHelpScreen = true; - } - } - - function onScreenChanged(type, url) { - onHelpScreen = type === "Web" && (url.indexOf(HELP_URL) === 0); - button.editProperties({ isActive: onHelpScreen }); - } - - button.clicked.connect(onClicked); - tablet.screenChanged.connect(onScreenChanged); - - Script.scriptEnding.connect(function () { - if (onHelpScreen) { - tablet.gotoHomeScreen(); - } - button.clicked.disconnect(onClicked); - tablet.screenChanged.disconnect(onScreenChanged); - if (tablet) { - tablet.removeButton(button); - } - }); - +} +startup(); }()); // END LOCAL_SCOPE diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 7906a3c97f..89974f9409 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -9,9 +9,10 @@ --> + - + diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 5cd5f6d610..7cc5937536 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -6,12 +6,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -var entities = {}; -var selectedEntities = []; -var currentSortColumn = 'type'; -var currentSortOrder = 'des'; -var entityList = null; -var refreshEntityListTimer = null; +const ASCENDING_SORT = 1; +const DESCENDING_SORT = -1; const ASCENDING_STRING = '▴'; const DESCENDING_STRING = '▾'; const LOCKED_GLYPH = ""; @@ -23,14 +19,54 @@ const DELETE = 46; // Key code for the delete key. const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities. -debugPrint = function (message) { - console.log(message); +const COMPARE_ASCENDING = function(a, b) { + let va = a[currentSortColumn]; + let vb = b[currentSortColumn]; + + if (va < vb) { + return -1; + } else if (va > vb) { + return 1; + } else if (a.id < b.id) { + return -1; + } + + return 1; +} +const COMPARE_DESCENDING = function(a, b) { + return COMPARE_ASCENDING(b, a); +} + + +// List of all entities +let entities = [] +// List of all entities, indexed by Entity ID +var entitiesByID = {}; +// The filtered and sorted list of entities +var visibleEntities = []; + +var selectedEntities = []; +var currentSortColumn = 'type'; +var currentSortOrder = ASCENDING_SORT; + +const ENABLE_PROFILING = false; +var profileIndent = ''; +const PROFILE_NOOP = function(_name, fn, args) { + fn.apply(this, args); +} ; +const PROFILE = !ENABLE_PROFILING ? PROFILE_NOOP : function(name, fn, args) { + console.log("PROFILE-Web " + profileIndent + "(" + name + ") Begin"); + var previousIndent = profileIndent; + profileIndent += ' '; + var before = Date.now(); + fn.apply(this, args); + var delta = Date.now() - before; + profileIndent = previousIndent; + console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms"); }; function loaded() { openEventBridge(function() { - entityList = new List('entity-list', { valueNames: ['name', 'type', 'url', 'locked', 'visible'], page: MAX_ITEMS}); - entityList.clear(); elEntityTable = document.getElementById("entity-table"); elEntityTableBody = document.getElementById("entity-table-body"); elRefresh = document.getElementById("refresh"); @@ -89,10 +125,10 @@ function loaded() { }; function onRowClicked(clickEvent) { - var id = this.dataset.entityId; - var selection = [this.dataset.entityId]; + let entityID = this.dataset.entityID; + let selection = [entityID]; if (clickEvent.ctrlKey) { - var selectedIndex = selectedEntities.indexOf(id); + let selectedIndex = selectedEntities.indexOf(entityID); if (selectedIndex >= 0) { selection = selectedEntities; selection.splice(selectedIndex, 1) @@ -100,22 +136,23 @@ function loaded() { selection = selection.concat(selectedEntities); } } else if (clickEvent.shiftKey && selectedEntities.length > 0) { - var previousItemFound = -1; - var clickedItemFound = -1; - for (var entity in entityList.visibleItems) { - if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) { - clickedItemFound = entity; - } else if(previousItemFound === -1 && selectedEntities[0] == entityList.visibleItems[entity].values().id) { - previousItemFound = entity; + let previousItemFound = -1; + let clickedItemFound = -1; + for (let i = 0, len = visibleEntities.length; i < len; ++i) { + let entity = visibleEntities[i]; + if (clickedItemFound === -1 && entityID === entity.id) { + clickedItemFound = i; + } else if (previousItemFound === -1 && selectedEntities[0] === entity.id) { + previousItemFound = i; } - } + }; if (previousItemFound !== -1 && clickedItemFound !== -1) { - var betweenItems = []; - var toItem = Math.max(previousItemFound, clickedItemFound); + let betweenItems = []; + let toItem = Math.max(previousItemFound, clickedItemFound); // skip first and last item in this loop, we add them to selection after the loop - for (var i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) { - entityList.visibleItems[i].elm.className = 'selected'; - betweenItems.push(entityList.visibleItems[i].values().id); + for (let i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) { + visibleEntities[i].el.className = 'selected'; + betweenItems.push(visibleEntities[i].id); } if (previousItemFound > clickedItemFound) { // always make sure that we add the items in the right order @@ -125,6 +162,15 @@ function loaded() { } } + selectedEntities.forEach(function(entityID) { + if (selection.indexOf(entityID) === -1) { + let entity = entitiesByID[entityID]; + if (entity !== undefined) { + entity.el.className = ''; + } + } + }); + selectedEntities = selection; this.className = 'selected'; @@ -142,7 +188,7 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", focus: true, - entityIds: [this.dataset.entityId], + entityIds: [this.dataset.entityID], })); } @@ -156,64 +202,141 @@ function loaded() { return number ? number : ""; } - function addEntity(id, name, type, url, locked, visible, verticesCount, texturesCount, texturesSize, hasTransparent, - isBaked, drawCalls, hasScript) { + function getFilename(url) { + let urlParts = url.split('/'); + return urlParts[urlParts.length - 1]; + } - var urlParts = url.split('/'); - var filename = urlParts[urlParts.length - 1]; + // Update the entity list with the new set of data sent from edit.js + function updateEntityList(entityData) { + const IMAGE_MODEL_NAME = 'default-image-model.fbx'; - var IMAGE_MODEL_NAME = 'default-image-model.fbx'; + entities = [] + entitiesByID = {}; + visibleEntities = []; - if (filename === IMAGE_MODEL_NAME) { - type = "Image"; - } + PROFILE("map-data", function() { + entityData.forEach(function(entity) { + let type = entity.type; + let filename = getFilename(entity.url); + if (filename === IMAGE_MODEL_NAME) { + type = "Image"; + } - if (entities[id] === undefined) { - entityList.add([{ - id: id, name: name, type: type, url: filename, locked: locked, visible: visible, - verticesCount: displayIfNonZero(verticesCount), texturesCount: displayIfNonZero(texturesCount), - texturesSize: decimalMegabytes(texturesSize), hasTransparent: hasTransparent, - isBaked: isBaked, drawCalls: displayIfNonZero(drawCalls), hasScript: hasScript - }], - function (items) { - var currentElement = items[0].elm; - var id = items[0]._values.id; - entities[id] = { - id: id, - name: name, - el: currentElement, - item: items[0] - }; - currentElement.setAttribute('id', 'entity_' + id); - currentElement.setAttribute('title', url); - currentElement.dataset.entityId = id; - currentElement.onclick = onRowClicked; - currentElement.ondblclick = onRowDoubleClicked; + let entityData = { + id: entity.id, + name: entity.name, + type: type, + url: filename, + fullUrl: entity.url, + locked: entity.locked, + visible: entity.visible, + verticesCount: entity.verticesCount, + texturesCount: entity.texturesCount, + texturesSize: entity.texturesSize, + hasTransparent: entity.hasTransparent, + isBaked: entity.isBaked, + drawCalls: entity.drawCalls, + hasScript: entity.hasScript, + } + + entities.push(entityData); + entitiesByID[entityData.id] = entityData; }); - } else { - var item = entities[id].item; - item.values({ name: name, url: filename, locked: locked, visible: visible }); - } + }); + + PROFILE("create-rows", function() { + entities.forEach(function(entity) { + let row = document.createElement('tr'); + row.dataset.entityID = entity.id; + row.attributes.title = entity.fullUrl; + function addColumn(cls, text) { + let col = document.createElement('td'); + col.className = cls; + col.innerText = text; + row.append(col); + } + function addColumnHTML(cls, text) { + let col = document.createElement('td'); + col.className = cls; + col.innerHTML = text; + row.append(col); + } + addColumn('type', entity.type); + addColumn('name', entity.name); + addColumn('url', entity.url); + addColumnHTML('locked glyph', entity.locked ? LOCKED_GLYPH : null); + addColumnHTML('visible glyph', entity.visible ? VISIBLE_GLYPH : null); + addColumn('verticesCount', displayIfNonZero(entity.verticesCount)); + addColumn('texturesCount', displayIfNonZero(entity.texturesCount)); + addColumn('texturesSize', decimalMegabytes(entity.texturesSize)); + addColumnHTML('hasTransparent glyph', entity.hasTransparent ? TRANSPARENCY_GLYPH : null); + addColumnHTML('isBaked glyph', entity.isBaked ? BAKED_GLYPH : null); + addColumn('drawCalls', displayIfNonZero(entity.drawCalls)); + addColumn('hasScript glyph', entity.hasScript ? SCRIPT_GLYPH : null); + row.addEventListener('click', onRowClicked); + row.addEventListener('dblclick', onRowDoubleClicked); + + entity.el = row; + }); + }); + + refreshEntityList(); + updateSelectedEntities(selectedEntities); + } + + function refreshEntityList() { + PROFILE("refresh-entity-list", function() { + PROFILE("filter", function() { + let searchTerm = elFilter.value.toLowerCase(); + if (searchTerm === '') { + visibleEntities = entities.slice(0); + } else { + visibleEntities = entities.filter(function(e) { + return e.name.toLowerCase().indexOf(searchTerm) > -1 + || e.type.toLowerCase().indexOf(searchTerm) > -1 + || e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 + || e.id.toLowerCase().indexOf(searchTerm) > -1; + }); + } + }); + + PROFILE("sort", function() { + let cmp = currentSortOrder === ASCENDING_SORT ? COMPARE_ASCENDING : COMPARE_DESCENDING; + visibleEntities.sort(cmp); + }); + + PROFILE("update-dom", function() { + elEntityTableBody.innerHTML = ''; + for (let i = 0, len = visibleEntities.length; i < len; ++i) { + elEntityTableBody.append(visibleEntities[i].el); + } + }); + }); } function removeEntities(deletedIDs) { - for (i = 0, length = deletedIDs.length; i < length; i++) { - delete entities[deletedIDs[i]]; - entityList.remove("id", deletedIDs[i]); + // Loop from the back so we can pop items off while iterating + for (let j = entities.length - 1; j >= 0; --j) { + let id = entities[j]; + for (let i = 0, length = deletedIDs.length; i < length; ++i) { + if (id === deletedIDs[i]) { + entities.splice(j, 1); + entitiesByID[id].el.remove(); + delete entitiesByID[id]; + break; + } + } } - } - - function scheduleRefreshEntityList() { - var REFRESH_DELAY = 50; - if (refreshEntityListTimer) { - clearTimeout(refreshEntityListTimer); - } - refreshEntityListTimer = setTimeout(refreshEntityListObject, REFRESH_DELAY); + refreshEntities(); } function clearEntities() { - entities = {}; - entityList.clear(); + entities = [] + entitiesByID = {}; + visibleEntities = []; + elEntityTableBody.innerHTML = ''; + refreshFooter(); } @@ -232,15 +355,17 @@ function loaded() { hasScript: document.querySelector('#entity-hasScript .sort-order'), } function setSortColumn(column) { - if (currentSortColumn == column) { - currentSortOrder = currentSortOrder == "asc" ? "desc" : "asc"; - } else { - elSortOrder[currentSortColumn].innerHTML = ""; - currentSortColumn = column; - currentSortOrder = "asc"; - } - elSortOrder[column].innerHTML = currentSortOrder == "asc" ? ASCENDING_STRING : DESCENDING_STRING; - entityList.sort(currentSortColumn, { order: currentSortOrder }); + PROFILE("set-sort-column", function() { + if (currentSortColumn === column) { + currentSortOrder *= -1; + } else { + elSortOrder[currentSortColumn].innerHTML = ""; + currentSortColumn = column; + currentSortOrder = ASCENDING_SORT; + } + elSortOrder[column].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING; + refreshEntityList(); + }); } setSortColumn('type'); @@ -254,32 +379,30 @@ function loaded() { elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected"; } else if (selectedEntities.length === 1) { elFooter.firstChild.nodeValue = "1 entity selected"; - } else if (entityList.visibleItems.length === 1) { + } else if (visibleEntities.length === 1) { elFooter.firstChild.nodeValue = "1 entity found"; } else { - elFooter.firstChild.nodeValue = entityList.visibleItems.length + " entities found"; + elFooter.firstChild.nodeValue = visibleEntities.length + " entities found"; } } - function refreshEntityListObject() { - refreshEntityListTimer = null; - entityList.sort(currentSortColumn, { order: currentSortOrder }); - entityList.search(elFilter.value); - refreshFooter(); - } function updateSelectedEntities(selectedIDs) { - var notFound = false; - for (var id in entities) { - entities[id].el.className = ''; - } + let notFound = false; + + selectedEntities.forEach(function(id) { + let entity = entitiesByID[id]; + if (entity !== undefined) { + entity.el.className = ''; + } + }); selectedEntities = []; - for (var i = 0; i < selectedIDs.length; i++) { - var id = selectedIDs[i]; + for (let i = 0; i < selectedIDs.length; i++) { + let id = selectedIDs[i]; selectedEntities.push(id); - if (id in entities) { - var entity = entities[id]; + let entity = entitiesByID[id]; + if (entity !== undefined) { entity.el.className = 'selected'; } else { notFound = true; @@ -363,31 +486,20 @@ function loaded() { refreshEntities(); } } else if (data.type === "update" && data.selectedIDs !== undefined) { - var newEntities = data.entities; - if (newEntities && newEntities.length == 0) { - elNoEntitiesMessage.style.display = "block"; - elFooter.firstChild.nodeValue = "0 entities found"; - } else if (newEntities) { - elNoEntitiesMessage.style.display = "none"; - for (var i = 0; i < newEntities.length; i++) { - var id = newEntities[i].id; - addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url, - newEntities[i].locked ? LOCKED_GLYPH : null, - newEntities[i].visible ? VISIBLE_GLYPH : null, - newEntities[i].verticesCount, newEntities[i].texturesCount, newEntities[i].texturesSize, - newEntities[i].hasTransparent ? TRANSPARENCY_GLYPH : null, - newEntities[i].isBaked ? BAKED_GLYPH : null, - newEntities[i].drawCalls, - newEntities[i].hasScript ? SCRIPT_GLYPH : null); + PROFILE("update", function() { + var newEntities = data.entities; + if (newEntities && newEntities.length === 0) { + elNoEntitiesMessage.style.display = "block"; + elFooter.firstChild.nodeValue = "0 entities found"; + } else if (newEntities) { + elNoEntitiesMessage.style.display = "none"; + updateEntityList(newEntities); + updateSelectedEntities(data.selectedIDs); } - updateSelectedEntities(data.selectedIDs); - scheduleRefreshEntityList(); - resize(); - } + }); } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { removeEntities(data.deletedIDs); updateSelectedEntities(data.selectedIDs); - scheduleRefreshEntityList(); } else if (data.type === "deleted" && data.ids) { removeEntities(data.ids); refreshFooter(); @@ -434,7 +546,13 @@ function loaded() { }; window.onresize = resize; - elFilter.onchange = resize; + + elFilter.onkeyup = refreshEntityList; + elFilter.onpaste = refreshEntityList; + elFilter.onchange = function() { + refreshEntityList(); + resize(); + }; elFilter.onblur = refreshFooter; diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 9b91d06d41..3239105254 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -1,3 +1,5 @@ +/* global $, window, MutationObserver */ + // // marketplacesInject.js // @@ -11,7 +13,6 @@ // (function () { - // Event bridge messages. var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; var CLARA_IO_STATUS = "CLARA.IO STATUS"; @@ -24,7 +25,7 @@ var canWriteAssets = false; var xmlHttpRequest = null; - var isPreparing = false; // Explicitly track download request status. + var isPreparing = false; // Explicitly track download request status. var commerceMode = false; var userIsLoggedIn = false; @@ -33,7 +34,6 @@ var messagesWaiting = false; function injectCommonCode(isDirectoryPage) { - // Supporting styles from marketplaces.css. // Glyph font family, size, and spacing adjusted because HiFi-Glyphs cannot be used cross-domain. $("head").append( @@ -74,7 +74,9 @@ (document.referrer !== "") ? window.history.back() : window.location = (marketplaceBaseURL + "/marketplace?"); }); $("#all-markets").on("click", function () { - EventBridge.emitWebEvent(GOTO_DIRECTORY); + EventBridge.emitWebEvent(JSON.stringify({ + type: GOTO_DIRECTORY + })); }); } @@ -94,11 +96,11 @@ }); } - emitWalletSetupEvent = function() { + var emitWalletSetupEvent = function () { EventBridge.emitWebEvent(JSON.stringify({ type: "WALLET_SETUP" })); - } + }; function maybeAddSetupWalletButton() { if (!$('body').hasClass("walletsetup-injected") && userIsLoggedIn && walletNeedsSetup) { @@ -113,7 +115,7 @@ var span = document.createElement('span'); span.style = "margin:10px 5px;color:#1b6420;font-size:15px;"; - span.innerHTML = "Setup your Wallet to get money and shop in Marketplace."; + span.innerHTML = "Activate your Wallet to get money and shop in Marketplace."; var xButton = document.createElement('a'); xButton.id = "xButton"; @@ -285,7 +287,7 @@ $(this).closest('.col-xs-3').prev().attr("class", 'col-xs-6'); $(this).closest('.col-xs-3').attr("class", 'col-xs-6'); - var priceElement = $(this).find('.price') + var priceElement = $(this).find('.price'); priceElement.css({ "padding": "3px 5px", "height": "40px", @@ -355,12 +357,12 @@ function injectAddScrollbarToCategories() { $('#categories-dropdown').on('show.bs.dropdown', function () { $('body > div.container').css('display', 'none') - $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': 'auto', 'height': 'calc(100vh - 110px)' }) + $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': 'auto', 'height': 'calc(100vh - 110px)' }); }); $('#categories-dropdown').on('hide.bs.dropdown', function () { - $('body > div.container').css('display', '') - $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': '', 'height': '' }) + $('body > div.container').css('display', ''); + $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': '', 'height': '' }); }); } @@ -382,7 +384,6 @@ mutations.forEach(function (mutation) { injectBuyButtonOnMainPage(); }); - //observer.disconnect(); }); var config = { attributes: true, childList: true, characterData: true }; observer.observe(target, config); @@ -451,8 +452,8 @@ "itemPage", urlParams.get('edition'), type); - } - }); + } + }); maybeAddPurchasesButton(); } } @@ -503,127 +504,142 @@ $(".top-title .col-sm-4").append(downloadContainer); downloadContainer.append(downloadFBX); } + } + } - // Automatic download to High Fidelity. - function startAutoDownload() { + // Automatic download to High Fidelity. + function startAutoDownload() { + // One file request at a time. + if (isPreparing) { + console.log("WARNING: Clara.io FBX: Prepare only one download at a time"); + return; + } - // One file request at a time. - if (isPreparing) { - console.log("WARNING: Clara.io FBX: Prepare only one download at a time"); - return; - } + // User must be able to write to Asset Server. + if (!canWriteAssets) { + console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server"); + EventBridge.emitWebEvent(JSON.stringify({ + type: WARN_USER_NO_PERMISSIONS + })); + return; + } - // User must be able to write to Asset Server. - if (!canWriteAssets) { - console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server"); - EventBridge.emitWebEvent(WARN_USER_NO_PERMISSIONS); - return; - } + // User must be logged in. + var loginButton = $("#topnav a[href='/signup']"); + if (loginButton.length > 0) { + loginButton[0].click(); + return; + } - // User must be logged in. - var loginButton = $("#topnav a[href='/signup']"); - if (loginButton.length > 0) { - loginButton[0].click(); - return; - } + // Obtain zip file to download for requested asset. + // Reference: https://clara.io/learn/sdk/api/export - // Obtain zip file to download for requested asset. - // Reference: https://clara.io/learn/sdk/api/export + //var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; + // 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to + // be successful in generating zip files. + var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL"; - //var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; - // 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to - // be successful in generating zip files. - var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL"; + var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1]; + var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid); - var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1]; - var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid); + xmlHttpRequest = new XMLHttpRequest(); + var responseTextIndex = 0; + var zipFileURL = ""; - xmlHttpRequest = new XMLHttpRequest(); - var responseTextIndex = 0; - var zipFileURL = ""; + xmlHttpRequest.onreadystatechange = function () { + // Messages are appended to responseText; process the new ones. + var message = this.responseText.slice(responseTextIndex); + var statusMessage = ""; - xmlHttpRequest.onreadystatechange = function () { - // Messages are appended to responseText; process the new ones. - var message = this.responseText.slice(responseTextIndex); - var statusMessage = ""; + if (isPreparing) { // Ignore messages in flight after finished/cancelled. + var lines = message.split(/[\n\r]+/); - if (isPreparing) { // Ignore messages in flight after finished/cancelled. - var lines = message.split(/[\n\r]+/); + for (var i = 0, length = lines.length; i < length; i++) { + if (lines[i].slice(0, 5) === "data:") { + // Parse line. + var data; + try { + data = JSON.parse(lines[i].slice(5)); + } + catch (e) { + data = {}; + } - for (var i = 0, length = lines.length; i < length; i++) { - if (lines[i].slice(0, 5) === "data:") { - // Parse line. - var data; - try { - data = JSON.parse(lines[i].slice(5)); - } - catch (e) { - data = {}; - } + // Extract status message. + if (data.hasOwnProperty("message") && data.message !== null) { + statusMessage = data.message; + console.log("Clara.io FBX: " + statusMessage); + } - // Extract status message. - if (data.hasOwnProperty("message") && data.message !== null) { - statusMessage = data.message; - console.log("Clara.io FBX: " + statusMessage); - } - - // Extract zip file URL. - if (data.hasOwnProperty("files") && data.files.length > 0) { - zipFileURL = data.files[0].url; - if (zipFileURL.slice(-4) !== ".zip") { - console.log(JSON.stringify(data)); // Data for debugging. - } - } + // Extract zip file URL. + if (data.hasOwnProperty("files") && data.files.length > 0) { + zipFileURL = data.files[0].url; + if (zipFileURL.slice(-4) !== ".zip") { + console.log(JSON.stringify(data)); // Data for debugging. } } - - if (statusMessage !== "") { - // Update the UI with the most recent status message. - EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage); - } } - - responseTextIndex = this.responseText.length; - }; - - // Note: onprogress doesn't have computable total length so can't use it to determine % complete. - - xmlHttpRequest.onload = function () { - var statusMessage = ""; - - if (!isPreparing) { - return; - } - - isPreparing = false; - - var HTTP_OK = 200; - if (this.status !== HTTP_OK) { - statusMessage = "Zip file request terminated with " + this.status + " " + this.statusText; - console.log("ERROR: Clara.io FBX: " + statusMessage); - EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage); - } else if (zipFileURL.slice(-4) !== ".zip") { - statusMessage = "Error creating zip file for download."; - console.log("ERROR: Clara.io FBX: " + statusMessage + ": " + zipFileURL); - EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage); - } else { - EventBridge.emitWebEvent(CLARA_IO_DOWNLOAD + " " + zipFileURL); - console.log("Clara.io FBX: File download initiated for " + zipFileURL); - } - - xmlHttpRequest = null; } - isPreparing = true; - - console.log("Clara.io FBX: Request zip file for " + uuid); - EventBridge.emitWebEvent(CLARA_IO_STATUS + " Initiating download"); - - xmlHttpRequest.open("POST", url, true); - xmlHttpRequest.setRequestHeader("Accept", "text/event-stream"); - xmlHttpRequest.send(); + if (statusMessage !== "") { + // Update the UI with the most recent status message. + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: statusMessage + })); + } } + + responseTextIndex = this.responseText.length; + }; + + // Note: onprogress doesn't have computable total length so can't use it to determine % complete. + + xmlHttpRequest.onload = function () { + var statusMessage = ""; + + if (!isPreparing) { + return; + } + + isPreparing = false; + + var HTTP_OK = 200; + if (this.status !== HTTP_OK) { + statusMessage = "Zip file request terminated with " + this.status + " " + this.statusText; + console.log("ERROR: Clara.io FBX: " + statusMessage); + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: statusMessage + })); + } else if (zipFileURL.slice(-4) !== ".zip") { + statusMessage = "Error creating zip file for download."; + console.log("ERROR: Clara.io FBX: " + statusMessage + ": " + zipFileURL); + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: (statusMessage + ": " + zipFileURL) + })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_DOWNLOAD + })); + console.log("Clara.io FBX: File download initiated for " + zipFileURL); + } + + xmlHttpRequest = null; } + + isPreparing = true; + + console.log("Clara.io FBX: Request zip file for " + uuid); + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: "Initiating download" + })); + + xmlHttpRequest.open("POST", url, true); + xmlHttpRequest.setRequestHeader("Accept", "text/event-stream"); + xmlHttpRequest.send(); } function injectClaraCode() { @@ -663,7 +679,9 @@ updateClaraCodeInterval = undefined; }); - EventBridge.emitWebEvent(QUERY_CAN_WRITE_ASSETS); + EventBridge.emitWebEvent(JSON.stringify({ + type: QUERY_CAN_WRITE_ASSETS + })); } function cancelClaraDownload() { @@ -673,7 +691,9 @@ xmlHttpRequest.abort(); xmlHttpRequest = null; console.log("Clara.io FBX: File download cancelled"); - EventBridge.emitWebEvent(CLARA_IO_CANCELLED_DOWNLOAD); + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_CANCELLED_DOWNLOAD + })); } } @@ -708,25 +728,22 @@ function onLoad() { EventBridge.scriptEventReceived.connect(function (message) { - if (message.slice(0, CAN_WRITE_ASSETS.length) === CAN_WRITE_ASSETS) { - canWriteAssets = message.slice(-4) === "true"; - } else if (message.slice(0, CLARA_IO_CANCEL_DOWNLOAD.length) === CLARA_IO_CANCEL_DOWNLOAD) { + message = JSON.parse(message); + if (message.type === CAN_WRITE_ASSETS) { + canWriteAssets = message.canWriteAssets; + } else if (message.type === CLARA_IO_CANCEL_DOWNLOAD) { cancelClaraDownload(); - } else { - var parsedJsonMessage = JSON.parse(message); - - if (parsedJsonMessage.type === "marketplaces") { - if (parsedJsonMessage.action === "commerceSetting") { - commerceMode = !!parsedJsonMessage.data.commerceMode; - userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn; - walletNeedsSetup = !!parsedJsonMessage.data.walletNeedsSetup; - marketplaceBaseURL = parsedJsonMessage.data.metaverseServerURL; - if (marketplaceBaseURL.indexOf('metaverse.') !== -1) { - marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', ''); - } - messagesWaiting = parsedJsonMessage.data.messagesWaiting; - injectCode(); + } else if (message.type === "marketplaces") { + if (message.action === "commerceSetting") { + commerceMode = !!message.data.commerceMode; + userIsLoggedIn = !!message.data.userIsLoggedIn; + walletNeedsSetup = !!message.data.walletNeedsSetup; + marketplaceBaseURL = message.data.metaverseServerURL; + if (marketplaceBaseURL.indexOf('metaverse.') !== -1) { + marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', ''); } + messagesWaiting = message.data.messagesWaiting; + injectCode(); } } }); @@ -739,6 +756,6 @@ } // Load / unload. - window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready(). - window.addEventListener("page:change", onLoad); // Triggered after Marketplace HTML is changed + window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready(). + window.addEventListener("page:change", onLoad); // Triggered after Marketplace HTML is changed }()); diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js new file mode 100644 index 0000000000..0c786dad87 --- /dev/null +++ b/scripts/system/interstitialPage.js @@ -0,0 +1,475 @@ +// +// interstitialPage.js +// scripts/system +// +// Created by Dante Ruiz on 08/02/2018. +// Copyright 2012 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 +// + +/* global Script, Controller, Overlays, Quat, MyAvatar, Entities, print, Vec3, AddressManager, Render, Window, Toolbars, + Camera, HMD, location, Account, Xform*/ + +(function() { + Script.include("/~/system/libraries/Xform.js"); + var DEBUG = false; + var MIN_LOADING_PROGRESS = 3.6; + var TOTAL_LOADING_PROGRESS = 3.8; + var EPSILON = 0.01; + var isVisible = false; + var VOLUME = 0.4; + var tune = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/crystals_and_voices.wav"); + var sample = null; + var MAX_LEFT_MARGIN = 1.9; + var INNER_CIRCLE_WIDTH = 4.7; + var DEFAULT_Z_OFFSET = 5.45; + var previousCameraMode = Camera.mode; + + var renderViewTask = Render.getConfig("RenderMainView"); + var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + var request = Script.require('request').request; + var BUTTON_PROPERTIES = { + text: "Interstitial" + }; + + var tablet = null; + var button = null; + + // Tips have a character limit of 69 + var userTips = [ + "Tip: Visit TheSpot to explore featured domains!", + "Tip: Visit our docs online to learn more about scripting!", + "Tip: Don't want others invading your personal space? Turn on the Bubble!", + "Tip: Want to make a friend? Shake hands with them in VR!", + "Tip: Enjoy live music? Visit Rust to dance your heart out!", + "Tip: Have you visited BodyMart to check out the new avatars recently?", + "Tip: Use the Create app to import models and create custom entities.", + "Tip: We're open source! Feel free to contribute to our code on GitHub!", + "Tip: What emotes have you used in the Emote app?", + "Tip: Take and share your snapshots with the everyone using the Snap app.", + "Tip: Did you know you can show websites in-world by creating a web entity?", + "Tip: Find out more information about domains by visiting our website!", + "Tip: Did you know you can get cool new apps from the Marketplace?", + "Tip: Print your snapshots from the Snap app to share with others!", + "Tip: Log in to make friends, visit new domains, and save avatars!" + ]; + + var DEFAULT_DIMENSIONS = { x: 24, y: 24, z: 24 }; + + var loadingSphereID = Overlays.addOverlay("model", { + name: "Loading-Sphere", + position: Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0.0, y: -1.0, z: 0.0}), Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.95, z: 0})), + orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), + url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/black-sphere.fbx", + dimensions: DEFAULT_DIMENSIONS, + alpha: 1, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + parentID: MyAvatar.SELF_ID + }); + + var anchorOverlay = Overlays.addOverlay("cube", { + dimensions: {x: 0.2, y: 0.2, z: 0.2}, + visible: false, + grabbable: false, + ignoreRayIntersection: true, + localPosition: {x: 0.0, y: getAnchorLocalYOffset(), z: DEFAULT_Z_OFFSET }, + orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), + solid: true, + drawInFront: true, + parentID: loadingSphereID + }); + + + var domainName = ""; + var domainNameTextID = Overlays.addOverlay("text3d", { + name: "Loading-Destination-Card-Text", + localPosition: { x: 0.0, y: 0.8, z: -0.001 }, + text: domainName, + textAlpha: 1, + backgroundAlpha: 1, + lineHeight: 0.42, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + parentID: anchorOverlay + }); + + var domainText = ""; + var domainDescription = Overlays.addOverlay("text3d", { + name: "Loading-Hostname", + localPosition: { x: 0.0, y: 0.32, z: 0.0 }, + text: domainText, + textAlpha: 1, + backgroundAlpha: 1, + lineHeight: 0.13, + visible: isVisible, + backgroundAlpha: 0, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + parentID: anchorOverlay + }); + + var toolTip = ""; + + var domainToolTip = Overlays.addOverlay("text3d", { + name: "Loading-Tooltip", + localPosition: { x: 0.0 , y: -1.6, z: 0.0 }, + text: toolTip, + textAlpha: 1, + backgroundAlpha: 1, + lineHeight: 0.13, + visible: isVisible, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + parentID: anchorOverlay + }); + + var loadingToTheSpotID = Overlays.addOverlay("image3d", { + name: "Loading-Destination-Card-Text", + localPosition: { x: 0.0 , y: -1.5, z: -0.3 }, + url: Script.resourcesPath() + "images/interstitialPage/goTo_button.png", + alpha: 1, + visible: isVisible, + emissive: true, + ignoreRayIntersection: false, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + parentID: anchorOverlay + }); + + var loadingBarPlacard = Overlays.addOverlay("image3d", { + name: "Loading-Bar-Placard", + localPosition: { x: 0.0, y: -0.99, z: 0.3 }, + url: Script.resourcesPath() + "images/loadingBar_placard.png", + alpha: 1, + dimensions: { x: 4, y: 2.8}, + visible: isVisible, + emissive: true, + ignoreRayIntersection: false, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + parentID: anchorOverlay + }); + + var loadingBarProgress = Overlays.addOverlay("image3d", { + name: "Loading-Bar-Progress", + localPosition: { x: 0.0, y: -0.90, z: 0.0 }, + url: Script.resourcesPath() + "images/loadingBar_progress.png", + alpha: 1, + dimensions: {x: 3.8, y: 2.8}, + visible: isVisible, + emissive: true, + ignoreRayIntersection: false, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + parentID: anchorOverlay + }); + + var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update + var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; + var lastInterval = Date.now(); + var currentDomain = "no domain"; + var timer = null; + var target = 0; + + var connectionToDomainFailed = false; + + + function getAnchorLocalYOffset() { + var loadingSpherePosition = Overlays.getProperty(loadingSphereID, "position"); + var loadingSphereOrientation = Overlays.getProperty(loadingSphereID, "rotation"); + var overlayXform = new Xform(loadingSphereOrientation, loadingSpherePosition); + var worldToOverlayXform = overlayXform.inv(); + var headPosition = MyAvatar.getHeadPosition(); + var headPositionInOverlaySpace = worldToOverlayXform.xformPoint(headPosition); + return headPositionInOverlaySpace.y; + } + + function getLeftMargin(overlayID, text) { + var textSize = Overlays.textSize(overlayID, text); + var sizeDifference = ((INNER_CIRCLE_WIDTH - textSize.width) / 2); + var leftMargin = -(MAX_LEFT_MARGIN - sizeDifference); + return leftMargin; + } + + function lerp(a, b, t) { + return ((1 - t) * a + t * b); + } + + function resetValues() { + var properties = { + localPosition: { x: 1.85, y: -0.935, z: 0.0 }, + dimensions: { + x: 0.1, + y: 2.8 + } + }; + + Overlays.editOverlay(loadingBarProgress, properties); + } + + function startInterstitialPage() { + if (timer === null) { + updateOverlays(false); + startAudio(); + target = 0; + currentProgress = 0.1; + connectionToDomainFailed = false; + previousCameraMode = Camera.mode; + Camera.mode = "first person"; + timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); + } + } + + function startAudio() { + sample = Audio.playSound(tune, { + localOnly: true, + position: MyAvatar.getHeadPosition(), + volume: VOLUME + }); + } + + function endAudio() { + sample.stop(); + sample = null; + } + + function domainChanged(domain) { + if (domain !== currentDomain) { + MyAvatar.restoreAnimation(); + var name = location.placename; + domainName = name.charAt(0).toUpperCase() + name.slice(1); + var doRequest = true; + if (name.length === 0 && location.href === "file:///~/serverless/tutorial.json") { + domainName = "Serveless Domain (Tutorial)"; + doRequest = false; + } + var domainNameLeftMargin = getLeftMargin(domainNameTextID, domainName); + var textProperties = { + text: domainName, + leftMargin: domainNameLeftMargin + }; + + if (doRequest) { + var url = Account.metaverseServerURL + '/api/v1/places/' + domain; + request({ + uri: url + }, function(error, data) { + if (data.status === "success") { + var domainInfo = data.data; + var domainDescriptionText = domainInfo.place.description; + var leftMargin = getLeftMargin(domainDescription, domainDescriptionText); + var domainDescriptionProperties = { + text: domainDescriptionText, + leftMargin: leftMargin + }; + Overlays.editOverlay(domainDescription, domainDescriptionProperties); + } + }); + } else { + var domainDescriptionProperties = { + text: "" + }; + Overlays.editOverlay(domainDescription, domainDescriptionProperties); + } + + var randomIndex = Math.floor(Math.random() * userTips.length); + var tip = userTips[randomIndex]; + var tipLeftMargin = getLeftMargin(domainToolTip, tip); + var toolTipProperties = { + text: tip, + leftMargin: tipLeftMargin + }; + + Overlays.editOverlay(domainNameTextID, textProperties); + Overlays.editOverlay(domainToolTip, toolTipProperties); + + + startInterstitialPage(); + currentDomain = domain; + } + } + + var THE_PLACE = (HifiAbout.buildVersion === "dev") ? "hifi://TheSpot-dev": "hifi://TheSpot"; + function clickedOnOverlay(overlayID, event) { + if (loadingToTheSpotID === overlayID) { + location.handleLookupString(THE_PLACE); + } + } + + var currentProgress = 0.1; + + function updateOverlays(physicsEnabled) { + var properties = { + visible: !physicsEnabled + }; + + var mainSphereProperties = { + visible: !physicsEnabled + }; + + var domainTextProperties = { + text: domainText, + visible: !physicsEnabled + }; + + var loadingBarProperties = { + dimensions: { x: 0.0, y: 2.8 }, + visible: !physicsEnabled + }; + + if (!HMD.active) { + MyAvatar.headOrientation = Quat.multiply(Quat.cancelOutRollAndPitch(MyAvatar.headOrientation), Quat.fromPitchYawRollDegrees(-3.0, 0, 0)); + } + + renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = physicsEnabled; + renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = physicsEnabled; + renderViewTask.getConfig("LightingModel")["enablePointLight"] = physicsEnabled; + Overlays.editOverlay(loadingSphereID, mainSphereProperties); + Overlays.editOverlay(loadingToTheSpotID, properties); + Overlays.editOverlay(domainNameTextID, properties); + Overlays.editOverlay(domainDescription, domainTextProperties); + Overlays.editOverlay(domainToolTip, properties); + Overlays.editOverlay(loadingBarPlacard, properties); + Overlays.editOverlay(loadingBarProgress, loadingBarProperties); + + + Menu.setIsOptionChecked("Show Overlays", physicsEnabled); + if (!HMD.active) { + toolbar.writeProperty("visible", physicsEnabled); + } + + resetValues(); + + if (physicsEnabled) { + Camera.mode = previousCameraMode; + } + } + + + function scaleInterstitialPage(sensorToWorldScale) { + var yOffset = getAnchorLocalYOffset(); + var localPosition = { + x: 0.0, + y: yOffset, + z: 5.45 + }; + + Overlays.editOverlay(anchorOverlay, { localPosition: localPosition }); + } + + function update() { + var physicsEnabled = Window.isPhysicsEnabled(); + var thisInterval = Date.now(); + var deltaTime = (thisInterval - lastInterval); + lastInterval = thisInterval; + + var domainLoadingProgressPercentage = Window.domainLoadingProgress(); + + var progress = MIN_LOADING_PROGRESS * domainLoadingProgressPercentage; + if (progress >= target) { + target = progress; + } + + if ((physicsEnabled && (currentProgress < TOTAL_LOADING_PROGRESS))) { + target = TOTAL_LOADING_PROGRESS; + } + + currentProgress = lerp(currentProgress, target, 0.2); + var properties = { + localPosition: { x: (1.85 - (currentProgress / 2) - (-0.029 * (currentProgress / TOTAL_LOADING_PROGRESS))), y: -0.935, z: 0.0 }, + dimensions: { + x: currentProgress, + y: 2.8 + } + }; + + Overlays.editOverlay(loadingBarProgress, properties); + if ((physicsEnabled && (currentProgress >= (TOTAL_LOADING_PROGRESS - EPSILON)))) { + updateOverlays((physicsEnabled || connectionToDomainFailed)); + endAudio(); + currentDomain = "no domain"; + timer = null; + return; + } + + timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); + } + var whiteColor = {red: 255, green: 255, blue: 255}; + var greyColor = {red: 125, green: 125, blue: 125}; + Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay); + Overlays.hoverEnterOverlay.connect(function(overlayID, event) { + if (overlayID === loadingToTheSpotID) { + Overlays.editOverlay(loadingToTheSpotID, { color: greyColor }); + } + }); + + Overlays.hoverLeaveOverlay.connect(function(overlayID, event) { + if (overlayID === loadingToTheSpotID) { + Overlays.editOverlay(loadingToTheSpotID, { color: whiteColor }); + } + }); + + location.hostChanged.connect(domainChanged); + location.lookupResultsFinished.connect(function() { + Script.setTimeout(function() { + connectionToDomainFailed = !location.isConnected; + }, 1200); + }); + + MyAvatar.sensorToWorldScaleChanged.connect(scaleInterstitialPage); + MyAvatar.sessionUUIDChanged.connect(function() { + var avatarSessionUUID = MyAvatar.sessionUUID; + Overlays.editOverlay(loadingSphereID, { parentID: avatarSessionUUID }); + }); + + var toggle = true; + if (DEBUG) { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton(BUTTON_PROPERTIES); + + button.clicked.connect(function() { + toggle = !toggle; + updateOverlays(toggle); + }); + } + + function cleanup() { + Overlays.deleteOverlay(loadingSphereID); + Overlays.deleteOverlay(loadingToTheSpotID); + Overlays.deleteOverlay(domainNameTextID); + Overlays.deleteOverlay(domainDescription); + Overlays.deleteOverlay(domainToolTip); + Overlays.deleteOverlay(loadingBarPlacard); + Overlays.deleteOverlay(loadingBarProgress); + Overlays.deleteOverlay(anchorOverlay); + + if (DEBUG) { + tablet.removeButton(button); + } + + renderViewTask.getConfig("LightingModel")["enableAmbientLight"] = true; + renderViewTask.getConfig("LightingModel")["enableDirectionalLight"] = true; + renderViewTask.getConfig("LightingModel")["enablePointLight"] = true; + Menu.setIsOptionChecked("Show Overlays", true); + if (!HMD.active) { + toolbar.writeProperty("visible", true); + } + } + + Script.scriptEnding.connect(cleanup); +}()); diff --git a/scripts/system/libraries/EditEntityList.qml b/scripts/system/libraries/EditEntityList.qml new file mode 100644 index 0000000000..d8099cb670 --- /dev/null +++ b/scripts/system/libraries/EditEntityList.qml @@ -0,0 +1,11 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtWebChannel 1.0 +import QtGraphicalEffects 1.0 +import "qrc:///qml/controls" as HifiControls + +HifiControls.WebView { + id: entityListToolWebView + url: Qt.resolvedUrl("../html/entityList.html") + enabled: true +} diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index a386dcf5b4..c34fd76802 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -5,7 +5,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global module, Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, +/* global module, Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, Mat4, Selection, Uuid, MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, FORBIDDEN_GRAB_TYPES:true, HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true, @@ -58,7 +58,8 @@ highlightTargetEntity:true, clearHighlightedEntities:true, unhighlightTargetEntity:true, - distanceBetweenEntityLocalPositionAndBoundingBox: true + distanceBetweenEntityLocalPositionAndBoundingBox: true, + worldPositionToRegistrationFrameMatrix: true */ MSECS_PER_SEC = 1000.0; @@ -487,6 +488,30 @@ entityIsFarGrabbedByOther = function(entityID) { return false; }; + +worldPositionToRegistrationFrameMatrix = function(wptrProps, pos) { + // get world matrix for intersection point + var intersectionMat = new Xform({ x: 0, y: 0, z:0, w: 1 }, pos); + + // calculate world matrix for registrationPoint addjusted entity + var DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 }; + var regRatio = Vec3.subtract(DEFAULT_REGISTRATION_POINT, wptrProps.registrationPoint); + var regOffset = Vec3.multiplyVbyV(regRatio, wptrProps.dimensions); + var regOffsetRot = Vec3.multiplyQbyV(wptrProps.rotation, regOffset); + var modelMat = new Xform(wptrProps.rotation, Vec3.sum(wptrProps.position, regOffsetRot)); + + // get inverse of model matrix + var modelMatInv = modelMat.inv(); + + // transform world intersection point into object's registrationPoint frame + var xformMat = Xform.mul(modelMatInv, intersectionMat); + + // convert to Mat4 + var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos); + return offsetMat; +}; + + if (typeof module !== 'undefined') { module.exports = { makeDispatcherModuleParameters: makeDispatcherModuleParameters, @@ -508,6 +533,7 @@ if (typeof module !== 'undefined') { projectOntoEntityXYPlane: projectOntoEntityXYPlane, TRIGGER_OFF_VALUE: TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE: TRIGGER_ON_VALUE, - DISPATCHER_HOVERING_LIST: DISPATCHER_HOVERING_LIST + DISPATCHER_HOVERING_LIST: DISPATCHER_HOVERING_LIST, + worldPositionToRegistrationFrameMatrix: worldPositionToRegistrationFrameMatrix }; } diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 678b2eeb0b..27e079589b 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -11,6 +11,22 @@ /* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages, cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible */ +var PROFILING_ENABLED = false; +var profileIndent = ''; +const PROFILE_NOOP = function(_name, fn, args) { + fn.apply(this, args); +} ; +PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) { + console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin"); + var previousIndent = profileIndent; + profileIndent += ' '; + var before = Date.now(); + fn.apply(this, args); + var delta = Date.now() - before; + profileIndent = previousIndent; + console.log("PROFILE-Script " + profileIndent + "(" + name + ") End " + delta + "ms"); +}; + EntityListTool = function(shouldUseEditTabletApp) { var that = {}; @@ -20,7 +36,7 @@ EntityListTool = function(shouldUseEditTabletApp) { var ENTITY_LIST_WIDTH = 495; var MAX_DEFAULT_CREATE_TOOLS_HEIGHT = 778; var entityListWindow = new CreateWindow( - Script.resourcesPath() + "qml/hifi/tablet/EditEntityList.qml", + Script.resolvePath("EditEntityList.qml"), 'Entity List', 'com.highfidelity.create.entityListWindow', function () { @@ -66,13 +82,18 @@ EntityListTool = function(shouldUseEditTabletApp) { that.setVisible(false); function emitJSONScriptEvent(data) { - var dataString = JSON.stringify(data); - webView.emitScriptEvent(dataString); - if (entityListWindow.window) { - entityListWindow.window.emitScriptEvent(dataString); - } + var dataString; + PROFILE("Script-JSON.stringify", function() { + dataString = JSON.stringify(data); + }); + PROFILE("Script-emitScriptEvent", function() { + webView.emitScriptEvent(dataString); + if (entityListWindow.window) { + entityListWindow.window.emitScriptEvent(dataString); + } + }); } - + that.toggleVisible = function() { that.setVisible(!visible); }; @@ -103,7 +124,7 @@ EntityListTool = function(shouldUseEditTabletApp) { selectedIDs: selectedIDs }); }; - + that.deleteEntities = function (deletedIDs) { emitJSONScriptEvent({ type: "deleted", @@ -116,59 +137,66 @@ EntityListTool = function(shouldUseEditTabletApp) { } that.sendUpdate = function() { - var entities = []; + PROFILE('Script-sendUpdate', function() { + var entities = []; - var ids; - if (filterInView) { - ids = Entities.findEntitiesInFrustum(Camera.frustum); - } else { - ids = Entities.findEntities(MyAvatar.position, searchRadius); - } - - var cameraPosition = Camera.position; - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - var properties = Entities.getEntityProperties(id); - - if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) { - var url = ""; - if (properties.type === "Model") { - url = properties.modelURL; - } else if (properties.type === "Material") { - url = properties.materialURL; + var ids; + PROFILE("findEntities", function() { + if (filterInView) { + ids = Entities.findEntitiesInFrustum(Camera.frustum); + } else { + ids = Entities.findEntities(MyAvatar.position, searchRadius); } - entities.push({ - id: id, - name: properties.name, - type: properties.type, - url: url, - locked: properties.locked, - visible: properties.visible, - verticesCount: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.verticesCount) : ""), - texturesCount: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.texturesCount) : ""), - texturesSize: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.texturesSize) : ""), - hasTransparent: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.hasTransparent) : ""), - isBaked: properties.type === "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false, - drawCalls: (properties.renderInfo !== undefined ? - valueIfDefined(properties.renderInfo.drawCalls) : ""), - hasScript: properties.script !== "" - }); + }); + + var cameraPosition = Camera.position; + PROFILE("getProperties", function() { + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + var properties = Entities.getEntityProperties(id, ['name', 'type', 'locked', + 'visible', 'renderInfo', 'type', 'modelURL', 'materialURL', 'script']); + + if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) { + var url = ""; + if (properties.type === "Model") { + url = properties.modelURL; + } else if (properties.type === "Material") { + url = properties.materialURL; + } + entities.push({ + id: id, + name: properties.name, + type: properties.type, + url: url, + locked: properties.locked, + visible: properties.visible, + verticesCount: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.verticesCount) : ""), + texturesCount: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.texturesCount) : ""), + texturesSize: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.texturesSize) : ""), + hasTransparent: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.hasTransparent) : ""), + isBaked: properties.type === "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false, + drawCalls: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.drawCalls) : ""), + hasScript: properties.script !== "" + }); + } + } + }); + + var selectedIDs = []; + for (var j = 0; j < selectionManager.selections.length; j++) { + selectedIDs.push(selectionManager.selections[j]); } - } - var selectedIDs = []; - for (var j = 0; j < selectionManager.selections.length; j++) { - selectedIDs.push(selectionManager.selections[j]); - } - - emitJSONScriptEvent({ - type: "update", - entities: entities, - selectedIDs: selectedIDs, + emitJSONScriptEvent({ + type: "update", + entities: entities, + selectedIDs: selectedIDs, + }); }); }; diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 1c7d5244a1..1fc0f0611c 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -14,7 +14,7 @@ // /* global SelectionManager, SelectionDisplay, grid, rayPlaneIntersection, rayPlaneIntersection2, pushCommandForSelections, - getMainTabletIDs, getControllerWorldLocation */ + getMainTabletIDs, getControllerWorldLocation, TRIGGER_ON_VALUE */ var SPACE_LOCAL = "local"; var SPACE_WORLD = "world"; @@ -22,23 +22,13 @@ var HIGHLIGHT_LIST_NAME = "editHandleHighlightList"; Script.include([ "./controllers.js", + "./controllerDispatcherUtils.js", "./utils.js" ]); SelectionManager = (function() { var that = {}; - /** - * @description Removes known to be broken properties from a properties object - * @param properties - * @return properties - */ - var fixRemoveBrokenProperties = function (properties) { - // Reason: Entity property is always set to 0,0,0 which causes it to override angularVelocity (see MS17131) - delete properties.localAngularVelocity; - return properties; - } - // FUNCTION: SUBSCRIBE TO UPDATE MESSAGES function subscribeToUpdateMessages() { Messages.subscribe("entityToolUpdates"); @@ -129,7 +119,7 @@ SelectionManager = (function() { that.savedProperties = {}; for (var i = 0; i < that.selections.length; i++) { var entityID = that.selections[i]; - that.savedProperties[entityID] = fixRemoveBrokenProperties(Entities.getEntityProperties(entityID)); + that.savedProperties[entityID] = Entities.getEntityProperties(entityID); } }; @@ -257,7 +247,7 @@ SelectionManager = (function() { var originalEntityID = entitiesToDuplicate[i]; var properties = that.savedProperties[originalEntityID]; if (properties === undefined) { - properties = fixRemoveBrokenProperties(Entities.getEntityProperties(originalEntityID)); + properties = Entities.getEntityProperties(originalEntityID); } if (!properties.locked && (!properties.clientOnly || properties.owningAvatarID === MyAvatar.sessionUUID)) { if (nonDynamicEntityIsBeingGrabbedByAvatar(properties)) { @@ -410,7 +400,7 @@ SelectionDisplay = (function() { var COLOR_RED = { red: 226, green: 51, blue: 77 }; var COLOR_HOVER = { red: 227, green: 227, blue: 227 }; var COLOR_ROTATE_CURRENT_RING = { red: 255, green: 99, blue: 9 }; - var COLOR_SCALE_EDGE = { red: 87, green: 87, blue: 87 }; + var COLOR_BOUNDING_EDGE = { red: 87, green: 87, blue: 87 }; var COLOR_SCALE_CUBE = { red: 106, green: 106, blue: 106 }; var COLOR_SCALE_CUBE_SELECTED = { red: 18, green: 18, blue: 18 }; @@ -433,21 +423,23 @@ SelectionDisplay = (function() { var ROTATE_DISPLAY_SIZE_Y_MULTIPLIER = 0.09; var ROTATE_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.07; - var STRETCH_SPHERE_OFFSET = 0.06; - var STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE = 0.01; + var STRETCH_CUBE_OFFSET = 0.06; + var STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.02; var STRETCH_MINIMUM_DIMENSION = 0.001; var STRETCH_ALL_MINIMUM_DIMENSION = 0.01; - var STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE = 6; + var STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE = 2; var STRETCH_PANEL_WIDTH = 0.01; - var SCALE_CUBE_OFFSET = 0.5; - var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.0125; + var BOUNDING_EDGE_OFFSET = 0.5; + var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.02; var CLONER_OFFSET = { x: 0.9, y: -0.9, z: 0.9 }; var CTRL_KEY_CODE = 16777249; var RAIL_AXIS_LENGTH = 10000; + + var NO_HAND = -1; var TRANSLATE_DIRECTION = { X: 0, @@ -478,8 +470,6 @@ SelectionDisplay = (function() { YAW: 1, ROLL: 2 }; - - var NO_TRIGGER_HAND = -1; var spaceMode = SPACE_LOCAL; var overlayNames = []; @@ -495,6 +485,8 @@ SelectionDisplay = (function() { var worldRotationX; var worldRotationY; var worldRotationZ; + + var activeStretchCubePanelOffset = null; var previousHandle = null; var previousHandleHelper = null; @@ -589,20 +581,18 @@ SelectionDisplay = (function() { leftMargin: 0 }); - var handlePropertiesStretchSpheres = { - alpha: 1, - shape: "Sphere", + var handlePropertiesStretchCubes = { solid: true, visible: false, ignoreRayIntersection: false, drawInFront: true }; - var handleStretchXSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchXSphere, { color: COLOR_RED }); - var handleStretchYSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchYSphere, { color: COLOR_GREEN }); - var handleStretchZSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchZSphere, { color: COLOR_BLUE }); + var handleStretchXCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchXCube, { color: COLOR_RED }); + var handleStretchYCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchYCube, { color: COLOR_GREEN }); + var handleStretchZCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchZCube, { color: COLOR_BLUE }); var handlePropertiesStretchPanel = { shape: "Quad", @@ -619,8 +609,7 @@ SelectionDisplay = (function() { var handleStretchZPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); Overlays.editOverlay(handleStretchZPanel, { color: COLOR_BLUE }); - var handlePropertiesScaleCubes = { - alpha: 1, + var handleScaleCube = Overlays.addOverlay("cube", { size: 0.025, color: COLOR_SCALE_CUBE, solid: true, @@ -628,36 +617,28 @@ SelectionDisplay = (function() { ignoreRayIntersection: false, drawInFront: true, borderSize: 1.4 - }; - var handleScaleLBNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, -y, -z) - var handleScaleRBNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, -y, -z) - var handleScaleLBFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, -y, z) - var handleScaleRBFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, -y, z) - var handleScaleLTNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, y, -z) - var handleScaleRTNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, -z) - var handleScaleLTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, y, z) - var handleScaleRTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, z) + }); - var handlePropertiesScaleEdge = { + var handlePropertiesBoundingEdge = { alpha: 1, - color: COLOR_SCALE_EDGE, + color: COLOR_BOUNDING_EDGE, visible: false, ignoreRayIntersection: true, drawInFront: true, lineWidth: 0.2 }; - var handleScaleTREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleTLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleTFEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleTNEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleBREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleBLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleBFEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleBNEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleNREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleNLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleFREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); - var handleScaleFLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); + var handleBoundingTREdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingTLEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingTFEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingTNEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingBREdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingBLEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingBFEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingBNEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingNREdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingNLEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingFREdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + var handleBoundingFLEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); var handleCloner = Overlays.addOverlay("cube", { alpha: 1, @@ -738,32 +719,25 @@ SelectionDisplay = (function() { handleRotateRollRing, handleRotateCurrentRing, rotationDegreesDisplay, - handleStretchXSphere, - handleStretchYSphere, - handleStretchZSphere, + handleStretchXCube, + handleStretchYCube, + handleStretchZCube, handleStretchXPanel, handleStretchYPanel, handleStretchZPanel, - handleScaleLBNCube, - handleScaleRBNCube, - handleScaleLBFCube, - handleScaleRBFCube, - handleScaleLTNCube, - handleScaleRTNCube, - handleScaleLTFCube, - handleScaleRTFCube, - handleScaleTREdge, - handleScaleTLEdge, - handleScaleTFEdge, - handleScaleTNEdge, - handleScaleBREdge, - handleScaleBLEdge, - handleScaleBFEdge, - handleScaleBNEdge, - handleScaleNREdge, - handleScaleNLEdge, - handleScaleFREdge, - handleScaleFLEdge, + handleScaleCube, + handleBoundingTREdge, + handleBoundingTLEdge, + handleBoundingTFEdge, + handleBoundingTNEdge, + handleBoundingBREdge, + handleBoundingBLEdge, + handleBoundingBFEdge, + handleBoundingBNEdge, + handleBoundingNREdge, + handleBoundingNLEdge, + handleBoundingFREdge, + handleBoundingFLEdge, handleCloner, selectionBox, iconSelectionBox, @@ -787,34 +761,27 @@ SelectionDisplay = (function() { overlayNames[handleRotateCurrentRing] = "handleRotateCurrentRing"; overlayNames[rotationDegreesDisplay] = "rotationDegreesDisplay"; - overlayNames[handleStretchXSphere] = "handleStretchXSphere"; - overlayNames[handleStretchYSphere] = "handleStretchYSphere"; - overlayNames[handleStretchZSphere] = "handleStretchZSphere"; + overlayNames[handleStretchXCube] = "handleStretchXCube"; + overlayNames[handleStretchYCube] = "handleStretchYCube"; + overlayNames[handleStretchZCube] = "handleStretchZCube"; overlayNames[handleStretchXPanel] = "handleStretchXPanel"; overlayNames[handleStretchYPanel] = "handleStretchYPanel"; overlayNames[handleStretchZPanel] = "handleStretchZPanel"; - overlayNames[handleScaleLBNCube] = "handleScaleLBNCube"; - overlayNames[handleScaleRBNCube] = "handleScaleRBNCube"; - overlayNames[handleScaleLBFCube] = "handleScaleLBFCube"; - overlayNames[handleScaleRBFCube] = "handleScaleRBFCube"; - overlayNames[handleScaleLTNCube] = "handleScaleLTNCube"; - overlayNames[handleScaleRTNCube] = "handleScaleRTNCube"; - overlayNames[handleScaleLTFCube] = "handleScaleLTFCube"; - overlayNames[handleScaleRTFCube] = "handleScaleRTFCube"; + overlayNames[handleScaleCube] = "handleScaleCube"; - overlayNames[handleScaleTREdge] = "handleScaleTREdge"; - overlayNames[handleScaleTLEdge] = "handleScaleTLEdge"; - overlayNames[handleScaleTFEdge] = "handleScaleTFEdge"; - overlayNames[handleScaleTNEdge] = "handleScaleTNEdge"; - overlayNames[handleScaleBREdge] = "handleScaleBREdge"; - overlayNames[handleScaleBLEdge] = "handleScaleBLEdge"; - overlayNames[handleScaleBFEdge] = "handleScaleBFEdge"; - overlayNames[handleScaleBNEdge] = "handleScaleBNEdge"; - overlayNames[handleScaleNREdge] = "handleScaleNREdge"; - overlayNames[handleScaleNLEdge] = "handleScaleNLEdge"; - overlayNames[handleScaleFREdge] = "handleScaleFREdge"; - overlayNames[handleScaleFLEdge] = "handleScaleFLEdge"; + overlayNames[handleBoundingTREdge] = "handleBoundingTREdge"; + overlayNames[handleBoundingTLEdge] = "handleBoundingTLEdge"; + overlayNames[handleBoundingTFEdge] = "handleBoundingTFEdge"; + overlayNames[handleBoundingTNEdge] = "handleBoundingTNEdge"; + overlayNames[handleBoundingBREdge] = "handleBoundingBREdge"; + overlayNames[handleBoundingBLEdge] = "handleBoundingBLEdge"; + overlayNames[handleBoundingBFEdge] = "handleBoundingBFEdge"; + overlayNames[handleBoundingBNEdge] = "handleBoundingBNEdge"; + overlayNames[handleBoundingNREdge] = "handleBoundingNREdge"; + overlayNames[handleBoundingNLEdge] = "handleBoundingNLEdge"; + overlayNames[handleBoundingFREdge] = "handleBoundingFREdge"; + overlayNames[handleBoundingFLEdge] = "handleBoundingFLEdge"; overlayNames[handleCloner] = "handleCloner"; overlayNames[selectionBox] = "selectionBox"; @@ -825,11 +792,21 @@ SelectionDisplay = (function() { // We get mouseMoveEvents from the handControllers, via handControllerPointer. // But we dont' get mousePressEvents. - that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); - Script.scriptEnding.connect(that.triggerMapping.disable); - that.triggeredHand = NO_TRIGGER_HAND; + that.triggerClickMapping = Controller.newMapping(Script.resolvePath('') + '-click'); + that.triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); + that.triggeredHand = NO_HAND; + that.pressedHand = NO_HAND; that.triggered = function() { - return that.triggeredHand !== NO_TRIGGER_HAND; + return that.triggeredHand !== NO_HAND; + } + function pointingAtDesktopWindowOrTablet(hand) { + var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && + SelectionManager.pointingAtDesktopWindowRight) || + (hand === Controller.Standard.LeftHand && + SelectionManager.pointingAtDesktopWindowLeft); + var pointingAtTablet = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtTabletRight) || + (hand === Controller.Standard.LeftHand && SelectionManager.pointingAtTabletLeft); + return pointingAtDesktopWindow || pointingAtTablet; } function makeClickHandler(hand) { return function (clicked) { @@ -837,26 +814,39 @@ SelectionDisplay = (function() { if (that.triggered() && hand !== that.triggeredHand) { return; } - if (!that.triggered() && clicked) { - var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && - SelectionManager.pointingAtDesktopWindowRight) || - (hand === Controller.Standard.LeftHand && - SelectionManager.pointingAtDesktopWindowLeft); - var pointingAtTablet = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtTabletRight) || - (hand === Controller.Standard.LeftHand && SelectionManager.pointingAtTabletLeft); - if (pointingAtDesktopWindow || pointingAtTablet) { - return; - } + if (!that.triggered() && clicked && !pointingAtDesktopWindowOrTablet(hand)) { that.triggeredHand = hand; that.mousePressEvent({}); } else if (that.triggered() && !clicked) { - that.triggeredHand = NO_TRIGGER_HAND; + that.triggeredHand = NO_HAND; that.mouseReleaseEvent({}); } }; } - that.triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); - that.triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); + function makePressHandler(hand) { + return function (value) { + if (value >= TRIGGER_ON_VALUE && !that.triggered() && !pointingAtDesktopWindowOrTablet(hand)) { + that.pressedHand = hand; + that.updateHighlight({}); + } else { + that.pressedHand = NO_HAND; + that.resetPreviousHandleColor(); + } + } + } + that.triggerClickMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); + that.triggerClickMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); + that.triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); + that.triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); + that.enableTriggerMapping = function() { + that.triggerClickMapping.enable(); + that.triggerPressMapping.enable(); + }; + that.disableTriggerMapping = function() { + that.triggerClickMapping.disable(); + that.triggerPressMapping.disable(); + } + Script.scriptEnding.connect(that.disableTriggerMapping); // FUNCTION DEF(s): Intersection Check Helpers function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { @@ -983,6 +973,61 @@ SelectionDisplay = (function() { } return Uuid.NULL; }; + + that.updateHighlight = function(event) { + // if no tool is active, then just look for handles to highlight... + var pickRay = generalComputePickRay(event.x, event.y); + var result = Overlays.findRayIntersection(pickRay); + var pickedColor; + var highlightNeeded = false; + + if (result.intersects) { + switch (result.overlayID) { + case handleTranslateXCone: + case handleTranslateXCylinder: + case handleRotatePitchRing: + case handleStretchXCube: + pickedColor = COLOR_RED; + highlightNeeded = true; + break; + case handleTranslateYCone: + case handleTranslateYCylinder: + case handleRotateYawRing: + case handleStretchYCube: + pickedColor = COLOR_GREEN; + highlightNeeded = true; + break; + case handleTranslateZCone: + case handleTranslateZCylinder: + case handleRotateRollRing: + case handleStretchZCube: + pickedColor = COLOR_BLUE; + highlightNeeded = true; + break; + case handleScaleCube: + pickedColor = COLOR_SCALE_CUBE; + highlightNeeded = true; + break; + default: + that.resetPreviousHandleColor(); + break; + } + + if (highlightNeeded) { + that.resetPreviousHandleColor(); + Overlays.editOverlay(result.overlayID, { color: COLOR_HOVER }); + previousHandle = result.overlayID; + previousHandleHelper = that.getHandleHelper(result.overlayID); + if (previousHandleHelper !== null) { + Overlays.editOverlay(previousHandleHelper, { color: COLOR_HOVER }); + } + previousHandleColor = pickedColor; + } + + } else { + that.resetPreviousHandleColor(); + } + }; // FUNCTION: MOUSE MOVE EVENT var lastMouseEvent = null; @@ -1010,66 +1055,8 @@ SelectionDisplay = (function() { return true; } - // if no tool is active, then just look for handles to highlight... - var pickRay = generalComputePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(pickRay); - var pickedColor; - var highlightNeeded = false; - - if (result.intersects) { - switch (result.overlayID) { - case handleTranslateXCone: - case handleTranslateXCylinder: - case handleRotatePitchRing: - case handleStretchXSphere: - pickedColor = COLOR_RED; - highlightNeeded = true; - break; - case handleTranslateYCone: - case handleTranslateYCylinder: - case handleRotateYawRing: - case handleStretchYSphere: - pickedColor = COLOR_GREEN; - highlightNeeded = true; - break; - case handleTranslateZCone: - case handleTranslateZCylinder: - case handleRotateRollRing: - case handleStretchZSphere: - pickedColor = COLOR_BLUE; - highlightNeeded = true; - break; - case handleScaleLBNCube: - case handleScaleRBNCube: - case handleScaleLBFCube: - case handleScaleRBFCube: - case handleScaleLTNCube: - case handleScaleRTNCube: - case handleScaleLTFCube: - case handleScaleRTFCube: - pickedColor = COLOR_SCALE_CUBE; - highlightNeeded = true; - break; - default: - that.resetPreviousHandleColor(); - break; - } - - if (highlightNeeded) { - that.resetPreviousHandleColor(); - Overlays.editOverlay(result.overlayID, { color: COLOR_HOVER }); - previousHandle = result.overlayID; - previousHandleHelper = that.getHandleHelper(result.overlayID); - if (previousHandleHelper !== null) { - Overlays.editOverlay(previousHandleHelper, { color: COLOR_HOVER }); - } - previousHandleColor = pickedColor; - } - - } else { - that.resetPreviousHandleColor(); - } - + that.updateHighlight(event); + if (wantDebug) { print("=============== eST::MouseMoveEvent END ======================="); } @@ -1165,9 +1152,10 @@ SelectionDisplay = (function() { } }; - function controllerComputePickRay() { - var controllerPose = getControllerWorldLocation(that.triggeredHand, true); - if (controllerPose.valid && that.triggered()) { + function controllerComputePickRay(hand) { + var hand = that.triggered() ? that.triggeredHand : that.pressedHand; + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { var controllerPosition = controllerPose.translation; // This gets point direction right, but if you want general quaternion it would be more complicated: var controllerDirection = Quat.getUp(controllerPose.rotation); @@ -1178,6 +1166,12 @@ SelectionDisplay = (function() { function generalComputePickRay(x, y) { return controllerComputePickRay() || Camera.computePickRay(x, y); } + + function getControllerAvatarFramePositionFromPickRay(pickRay) { + var controllerPosition = Vec3.subtract(pickRay.origin, MyAvatar.position); + controllerPosition = Vec3.multiplyQbyV(Quat.inverse(MyAvatar.orientation), controllerPosition); + return controllerPosition; + } function getDistanceToCamera(position) { var cameraPosition = Camera.getPosition(); @@ -1424,127 +1418,56 @@ SelectionDisplay = (function() { dimensions: arrowConeDimensions }); - // UPDATE SCALE CUBES - var scaleCubeOffsetX = SCALE_CUBE_OFFSET * dimensions.x; - var scaleCubeOffsetY = SCALE_CUBE_OFFSET * dimensions.y; - var scaleCubeOffsetZ = SCALE_CUBE_OFFSET * dimensions.z; - var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY; - var scaleLBNCubePosition = { x: -scaleCubeOffsetX, y: -scaleCubeOffsetY, z: -scaleCubeOffsetZ }; - scaleLBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBNCubePosition)); - var scaleLBNCubeToCamera = getDistanceToCamera(scaleLBNCubePosition); - var scaleRBNCubePosition = { x: scaleCubeOffsetX, y: -scaleCubeOffsetY, z: -scaleCubeOffsetZ }; - scaleRBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBNCubePosition)); - var scaleRBNCubeToCamera = getDistanceToCamera(scaleRBNCubePosition); - var scaleLBFCubePosition = { x: -scaleCubeOffsetX, y: -scaleCubeOffsetY, z: scaleCubeOffsetZ }; - scaleLBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBFCubePosition)); - var scaleLBFCubeToCamera = getDistanceToCamera(scaleLBFCubePosition); - var scaleRBFCubePosition = { x: scaleCubeOffsetX, y: -scaleCubeOffsetY, z: scaleCubeOffsetZ }; - scaleRBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBFCubePosition)); - var scaleRBFCubeToCamera = getDistanceToCamera(scaleRBFCubePosition); - var scaleLTNCubePosition = { x: -scaleCubeOffsetX, y: scaleCubeOffsetY, z: -scaleCubeOffsetZ }; - scaleLTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTNCubePosition)); - var scaleLTNCubeToCamera = getDistanceToCamera(scaleLTNCubePosition); - var scaleRTNCubePosition = { x: scaleCubeOffsetX, y: scaleCubeOffsetY, z: -scaleCubeOffsetZ }; - scaleRTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTNCubePosition)); - var scaleRTNCubeToCamera = getDistanceToCamera(scaleRTNCubePosition); - var scaleLTFCubePosition = { x: -scaleCubeOffsetX, y: scaleCubeOffsetY, z: scaleCubeOffsetZ }; - scaleLTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTFCubePosition)); - var scaleLTFCubeToCamera = getDistanceToCamera(scaleLTFCubePosition); - var scaleRTFCubePosition = { x: scaleCubeOffsetX, y: scaleCubeOffsetY, z: scaleCubeOffsetZ }; - scaleRTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTFCubePosition)); - var scaleRTFCubeToCamera = getDistanceToCamera(scaleRTFCubePosition); - - var scaleCubeToCamera = Math.min(scaleLBNCubeToCamera, scaleRBNCubeToCamera, scaleLBFCubeToCamera, - scaleRBFCubeToCamera, scaleLTNCubeToCamera, scaleRTNCubeToCamera, - scaleLTFCubeToCamera, scaleRTFCubeToCamera); - var scaleCubeDimension = scaleCubeToCamera * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE; - var scaleCubeDimensions = { x: scaleCubeDimension, y: scaleCubeDimension, z: scaleCubeDimension }; - - Overlays.editOverlay(handleScaleLBNCube, { - position: scaleLBNCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleRBNCube, { - position: scaleRBNCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleLBFCube, { - position: scaleLBFCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleRBFCube, { - position: scaleRBFCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleLTNCube, { - position: scaleLTNCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleRTNCube, { - position: scaleRTNCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleLTFCube, { - position: scaleLTFCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - Overlays.editOverlay(handleScaleRTFCube, { - position: scaleRTFCubePosition, - rotation: scaleCubeRotation, - dimensions: scaleCubeDimensions - }); - - // UPDATE SCALE EDGES - Overlays.editOverlay(handleScaleTREdge, { start: scaleRTNCubePosition, end: scaleRTFCubePosition }); - Overlays.editOverlay(handleScaleTLEdge, { start: scaleLTNCubePosition, end: scaleLTFCubePosition }); - Overlays.editOverlay(handleScaleTFEdge, { start: scaleLTFCubePosition, end: scaleRTFCubePosition }); - Overlays.editOverlay(handleScaleTNEdge, { start: scaleLTNCubePosition, end: scaleRTNCubePosition }); - Overlays.editOverlay(handleScaleBREdge, { start: scaleRBNCubePosition, end: scaleRBFCubePosition }); - Overlays.editOverlay(handleScaleBLEdge, { start: scaleLBNCubePosition, end: scaleLBFCubePosition }); - Overlays.editOverlay(handleScaleBFEdge, { start: scaleLBFCubePosition, end: scaleRBFCubePosition }); - Overlays.editOverlay(handleScaleBNEdge, { start: scaleLBNCubePosition, end: scaleRBNCubePosition }); - Overlays.editOverlay(handleScaleNREdge, { start: scaleRTNCubePosition, end: scaleRBNCubePosition }); - Overlays.editOverlay(handleScaleNLEdge, { start: scaleLTNCubePosition, end: scaleLBNCubePosition }); - Overlays.editOverlay(handleScaleFREdge, { start: scaleRTFCubePosition, end: scaleRBFCubePosition }); - Overlays.editOverlay(handleScaleFLEdge, { start: scaleLTFCubePosition, end: scaleLBFCubePosition }); - - // UPDATE STRETCH SPHERES - var stretchSphereDimension = rotateDimension * STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE / + // UPDATE SCALE CUBE + var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY; + var scaleCubeDimension = rotateDimension * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var stretchSphereDimensions = { x: stretchSphereDimension, y: stretchSphereDimension, z: stretchSphereDimension }; - var stretchSphereOffset = rotateDimension * STRETCH_SPHERE_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var stretchXPosition = { x: stretchSphereOffset, y: 0, z: 0 }; - stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition)); - Overlays.editOverlay(handleStretchXSphere, { - position: stretchXPosition, - dimensions: stretchSphereDimensions - }); - var stretchYPosition = { x: 0, y: stretchSphereOffset, z: 0 }; - stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition)); - Overlays.editOverlay(handleStretchYSphere, { - position: stretchYPosition, - dimensions: stretchSphereDimensions - }); - var stretchZPosition = { x: 0, y: 0, z: stretchSphereOffset }; - stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition)); - Overlays.editOverlay(handleStretchZSphere, { - position: stretchZPosition, - dimensions: stretchSphereDimensions + var scaleCubeDimensions = { x: scaleCubeDimension, y: scaleCubeDimension, z: scaleCubeDimension }; + Overlays.editOverlay(handleScaleCube, { + position: position, + rotation: scaleCubeRotation, + dimensions: scaleCubeDimensions }); + // UPDATE BOUNDING BOX EDGES + var edgeOffsetX = BOUNDING_EDGE_OFFSET * dimensions.x; + var edgeOffsetY = BOUNDING_EDGE_OFFSET * dimensions.y; + var edgeOffsetZ = BOUNDING_EDGE_OFFSET * dimensions.z; + var LBNPosition = { x: -edgeOffsetX, y: -edgeOffsetY, z: -edgeOffsetZ }; + LBNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LBNPosition)); + var RBNPosition = { x: edgeOffsetX, y: -edgeOffsetY, z: -edgeOffsetZ }; + RBNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RBNPosition)); + var LBFPosition = { x: -edgeOffsetX, y: -edgeOffsetY, z: edgeOffsetZ }; + LBFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LBFPosition)); + var RBFPosition = { x: edgeOffsetX, y: -edgeOffsetY, z: edgeOffsetZ }; + RBFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RBFPosition)); + var LTNPosition = { x: -edgeOffsetX, y: edgeOffsetY, z: -edgeOffsetZ }; + LTNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LTNPosition)); + var RTNPosition = { x: edgeOffsetX, y: edgeOffsetY, z: -edgeOffsetZ }; + RTNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RTNPosition)); + var LTFPosition = { x: -edgeOffsetX, y: edgeOffsetY, z: edgeOffsetZ }; + LTFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LTFPosition)); + var RTFPosition = { x: edgeOffsetX, y: edgeOffsetY, z: edgeOffsetZ }; + RTFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RTFPosition)); + Overlays.editOverlay(handleBoundingTREdge, { start: RTNPosition, end: RTFPosition }); + Overlays.editOverlay(handleBoundingTLEdge, { start: LTNPosition, end: LTFPosition }); + Overlays.editOverlay(handleBoundingTFEdge, { start: LTFPosition, end: RTFPosition }); + Overlays.editOverlay(handleBoundingTNEdge, { start: LTNPosition, end: RTNPosition }); + Overlays.editOverlay(handleBoundingBREdge, { start: RBNPosition, end: RBFPosition }); + Overlays.editOverlay(handleBoundingBLEdge, { start: LBNPosition, end: LBFPosition }); + Overlays.editOverlay(handleBoundingBFEdge, { start: LBFPosition, end: RBFPosition }); + Overlays.editOverlay(handleBoundingBNEdge, { start: LBNPosition, end: RBNPosition }); + Overlays.editOverlay(handleBoundingNREdge, { start: RTNPosition, end: RBNPosition }); + Overlays.editOverlay(handleBoundingNLEdge, { start: LTNPosition, end: LBNPosition }); + Overlays.editOverlay(handleBoundingFREdge, { start: RTFPosition, end: RBFPosition }); + Overlays.editOverlay(handleBoundingFLEdge, { start: LTFPosition, end: LBFPosition }); + // UPDATE STRETCH HIGHLIGHT PANELS - var scaleRBFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRBFCubePosition); - var scaleRTFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTFCubePosition); - var scaleLTNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleLTNCubePosition); - var scaleRTNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTNCubePosition); - var stretchPanelXDimensions = Vec3.subtract(scaleRTNCubePositionRotated, scaleRBFCubePositionRotated); + var RBFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RBFPosition); + var RTFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RTFPosition); + var LTNPositionRotated = Vec3.multiplyQbyV(rotationInverse, LTNPosition); + var RTNPositionRotated = Vec3.multiplyQbyV(rotationInverse, RTNPosition); + var stretchPanelXDimensions = Vec3.subtract(RTNPositionRotated, RBFPositionRotated); var tempY = Math.abs(stretchPanelXDimensions.y); stretchPanelXDimensions.x = STRETCH_PANEL_WIDTH; stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z); @@ -1555,7 +1478,7 @@ SelectionDisplay = (function() { rotation: rotationZ, dimensions: stretchPanelXDimensions }); - var stretchPanelYDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRTFCubePositionRotated); + var stretchPanelYDimensions = Vec3.subtract(LTNPositionRotated, RTFPositionRotated); var tempX = Math.abs(stretchPanelYDimensions.x); stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z); stretchPanelYDimensions.y = STRETCH_PANEL_WIDTH; @@ -1566,7 +1489,7 @@ SelectionDisplay = (function() { rotation: rotationY, dimensions: stretchPanelYDimensions }); - var stretchPanelZDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRBFCubePositionRotated); + var stretchPanelZDimensions = Vec3.subtract(LTNPositionRotated, RBFPositionRotated); tempX = Math.abs(stretchPanelZDimensions.x); stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y); stretchPanelZDimensions.y = tempX; @@ -1578,6 +1501,46 @@ SelectionDisplay = (function() { dimensions: stretchPanelZDimensions }); + // UPDATE STRETCH CUBES + var stretchCubeDimension = rotateDimension * STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE / + ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var stretchCubeDimensions = { x: stretchCubeDimension, y: stretchCubeDimension, z: stretchCubeDimension }; + var stretchCubeOffset = rotateDimension * STRETCH_CUBE_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var stretchXPosition, stretchYPosition, stretchZPosition; + if (isActiveTool(handleStretchXCube)) { + stretchXPosition = Vec3.subtract(stretchPanelXPosition, activeStretchCubePanelOffset); + } else { + stretchXPosition = { x: stretchCubeOffset, y: 0, z: 0 }; + stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition)); + } + if (isActiveTool(handleStretchYCube)) { + stretchYPosition = Vec3.subtract(stretchPanelYPosition, activeStretchCubePanelOffset); + } else { + stretchYPosition = { x: 0, y: stretchCubeOffset, z: 0 }; + stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition)); + } + if (isActiveTool(handleStretchZCube)) { + stretchZPosition = Vec3.subtract(stretchPanelZPosition, activeStretchCubePanelOffset); + } else { + stretchZPosition = { x: 0, y: 0, z: stretchCubeOffset }; + stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition)); + } + Overlays.editOverlay(handleStretchXCube, { + position: stretchXPosition, + rotation: rotationX, + dimensions: stretchCubeDimensions + }); + Overlays.editOverlay(handleStretchYCube, { + position: stretchYPosition, + rotation: rotationY, + dimensions: stretchCubeDimensions + }); + Overlays.editOverlay(handleStretchZCube, { + position: stretchZPosition, + rotation: rotationZ, + dimensions: stretchCubeDimensions + }); + // UPDATE SELECTION BOX (CURRENTLY INVISIBLE WITH 0 ALPHA FOR TRANSLATE XZ TOOL) var inModeRotate = isActiveTool(handleRotatePitchRing) || isActiveTool(handleRotateYawRing) || @@ -1622,20 +1585,15 @@ SelectionDisplay = (function() { that.setHandleRotateRollVisible(!activeTool || isActiveTool(handleRotateRollRing)); var showScaleStretch = !activeTool && SelectionManager.selections.length === 1 && spaceMode === SPACE_LOCAL; - that.setHandleStretchXVisible(showScaleStretch || isActiveTool(handleStretchXSphere)); - that.setHandleStretchYVisible(showScaleStretch || isActiveTool(handleStretchYSphere)); - that.setHandleStretchZVisible(showScaleStretch || isActiveTool(handleStretchZSphere)); - that.setHandleScaleCubeVisible(showScaleStretch || isActiveTool(handleScaleLBNCube) || - isActiveTool(handleScaleRBNCube) || isActiveTool(handleScaleLBFCube) || - isActiveTool(handleScaleRBFCube) || isActiveTool(handleScaleLTNCube) || - isActiveTool(handleScaleRTNCube) || isActiveTool(handleScaleLTFCube) || - isActiveTool(handleScaleRTFCube) || isActiveTool(handleStretchXSphere) || - isActiveTool(handleStretchYSphere) || isActiveTool(handleStretchZSphere)); + that.setHandleStretchXVisible(showScaleStretch || isActiveTool(handleStretchXCube)); + that.setHandleStretchYVisible(showScaleStretch || isActiveTool(handleStretchYCube)); + that.setHandleStretchZVisible(showScaleStretch || isActiveTool(handleStretchZCube)); + that.setHandleScaleCubeVisible(showScaleStretch || isActiveTool(handleScaleCube)); var showOutlineForZone = (SelectionManager.selections.length === 1 && typeof SelectionManager.savedProperties[SelectionManager.selections[0]] !== "undefined" && SelectionManager.savedProperties[SelectionManager.selections[0]].type === "Zone"); - that.setHandleScaleEdgeVisible(showOutlineForZone || (!isActiveTool(handleRotatePitchRing) && + that.setHandleBoundingEdgeVisible(showOutlineForZone || (!isActiveTool(handleRotatePitchRing) && !isActiveTool(handleRotateYawRing) && !isActiveTool(handleRotateRollRing))); @@ -1721,47 +1679,40 @@ SelectionDisplay = (function() { }; that.setHandleStretchXVisible = function(isVisible) { - Overlays.editOverlay(handleStretchXSphere, { visible: isVisible }); + Overlays.editOverlay(handleStretchXCube, { visible: isVisible }); }; that.setHandleStretchYVisible = function(isVisible) { - Overlays.editOverlay(handleStretchYSphere, { visible: isVisible }); + Overlays.editOverlay(handleStretchYCube, { visible: isVisible }); }; that.setHandleStretchZVisible = function(isVisible) { - Overlays.editOverlay(handleStretchZSphere, { visible: isVisible }); + Overlays.editOverlay(handleStretchZCube, { visible: isVisible }); }; // FUNCTION: SET HANDLE SCALE VISIBLE that.setHandleScaleVisible = function(isVisible) { that.setHandleScaleCubeVisible(isVisible); - that.setHandleScaleEdgeVisible(isVisible); + that.setHandleBoundingEdgeVisible(isVisible); }; that.setHandleScaleCubeVisible = function(isVisible) { - Overlays.editOverlay(handleScaleLBNCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleRBNCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleLBFCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleRBFCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleLTNCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleRTNCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleLTFCube, { visible: isVisible }); - Overlays.editOverlay(handleScaleRTFCube, { visible: isVisible }); + Overlays.editOverlay(handleScaleCube, { visible: isVisible }); }; - that.setHandleScaleEdgeVisible = function(isVisible) { - Overlays.editOverlay(handleScaleTREdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleTLEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleTFEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleTNEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleBREdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleBLEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleBFEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleBNEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleNREdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleNLEdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleFREdge, { visible: isVisible }); - Overlays.editOverlay(handleScaleFLEdge, { visible: isVisible }); + that.setHandleBoundingEdgeVisible = function(isVisible) { + Overlays.editOverlay(handleBoundingTREdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingTLEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingTFEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingTNEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingBREdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingBLEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingBFEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingBNEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingNREdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingNLEdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingFREdge, { visible: isVisible }); + Overlays.editOverlay(handleBoundingFLEdge, { visible: isVisible }); }; // FUNCTION: SET HANDLE CLONER VISIBLE @@ -2123,7 +2074,7 @@ SelectionDisplay = (function() { }; // TOOL DEFINITION: HANDLE STRETCH TOOL - function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset, stretchPanel, scaleHandle) { + function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset, stretchPanel, cubeHandle) { var directionFor3DStretch = directionVec; var distanceFor3DStretch = 0; var DISTANCE_INFLUENCE_THRESHOLD = 1.2; @@ -2155,8 +2106,12 @@ SelectionDisplay = (function() { var pickRayPosition3D = null; var rotation = null; var previousPickRay = null; + var beginMouseEvent = null; + var beginControllerPosition = null; - var onBegin = function(event, pickRay, pickResult) { + var onBegin = function(event, pickRay, pickResult) { + var proportional = directionEnum === STRETCH_DIRECTION.ALL; + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); initialProperties = properties; rotation = (spaceMode === SPACE_LOCAL) ? properties.rotation : Quat.IDENTITY; @@ -2244,9 +2199,7 @@ SelectionDisplay = (function() { } planeNormal = Vec3.multiplyQbyV(rotation, planeNormal); - lastPick = rayPlaneIntersection(pickRay, - pickRayPosition, - planeNormal); + lastPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); var planeNormal3D = { x: 0, @@ -2274,10 +2227,7 @@ SelectionDisplay = (function() { if (stretchPanel !== null) { Overlays.editOverlay(stretchPanel, { visible: true }); } - if (scaleHandle !== null) { - Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE_SELECTED }); - } - + var collisionToRemove = "myAvatar"; if (properties.collidesWith.indexOf(collisionToRemove) > -1) { var newCollidesWith = properties.collidesWith.replace(collisionToRemove, ""); @@ -2285,16 +2235,23 @@ SelectionDisplay = (function() { that.replaceCollisionsAfterStretch = true; } + if (!proportional) { + var stretchCubePosition = Overlays.getProperty(cubeHandle, "position"); + var stretchPanelPosition = Overlays.getProperty(stretchPanel, "position"); + activeStretchCubePanelOffset = Vec3.subtract(stretchPanelPosition, stretchCubePosition); + } + previousPickRay = pickRay; + beginMouseEvent = event; + if (that.triggered()) { + beginControllerPosition = getControllerAvatarFramePositionFromPickRay(pickRay); + } }; var onEnd = function(event, reason) { if (stretchPanel !== null) { Overlays.editOverlay(stretchPanel, { visible: false }); } - if (scaleHandle !== null) { - Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE }); - } if (that.replaceCollisionsAfterStretch) { var newCollidesWith = SelectionManager.savedProperties[SelectionManager.selections[0]].collidesWith; @@ -2302,6 +2259,8 @@ SelectionDisplay = (function() { that.replaceCollisionsAfterStretch = false; } + activeStretchCubePanelOffset = null; + pushCommandForSelections(); }; @@ -2325,13 +2284,15 @@ SelectionDisplay = (function() { if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, planeNormal)) { pickRay = previousPickRay; } + + var controllerPose = getControllerWorldLocation(that.triggeredHand, true); + var controllerTrigger = HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && + controllerPose.valid && that.triggered(); // Are we using handControllers or Mouse - only relevant for 3D tools - var controllerPose = getControllerWorldLocation(that.triggeredHand, true); var vector = null; var newPick = null; - if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && - controllerPose.valid && that.triggered() && directionFor3DStretch) { + if (controllerTrigger && directionFor3DStretch) { localDeltaPivot = deltaPivot3D; newPick = pickRay.origin; vector = Vec3.subtract(newPick, lastPick3D); @@ -2351,34 +2312,38 @@ SelectionDisplay = (function() { vector = grid.snapToSpacing(vector); var changeInDimensions = Vec3.multiply(NEGATE_VECTOR, vec3Mult(localSigns, vector)); - if (directionEnum === STRETCH_DIRECTION.ALL) { - var toCameraDistance = getDistanceToCamera(position); - var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; - changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); - } - + var newDimensions; if (proportional) { - var absoluteX = Math.abs(changeInDimensions.x); - var absoluteY = Math.abs(changeInDimensions.y); - var absoluteZ = Math.abs(changeInDimensions.z); - var percentChange = 0; - if (absoluteX > absoluteY && absoluteX > absoluteZ) { - percentChange = changeInDimensions.x / initialProperties.dimensions.x; - percentChange = changeInDimensions.x / initialDimensions.x; - } else if (absoluteY > absoluteZ) { - percentChange = changeInDimensions.y / initialProperties.dimensions.y; - percentChange = changeInDimensions.y / initialDimensions.y; + var viewportDimensions = Controller.getViewportDimensions(); + var toCameraDistance = getDistanceToCamera(position); + var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; + + var dimensionChange; + if (controllerTrigger) { + var controllerPosition = getControllerAvatarFramePositionFromPickRay(pickRay); + var vecControllerDifference = Vec3.subtract(controllerPosition, beginControllerPosition); + var controllerDifference = vecControllerDifference.x + vecControllerDifference.y + + vecControllerDifference.z; + dimensionChange = controllerDifference * dimensionsMultiple; } else { - percentChange = changeInDimensions.z / initialProperties.dimensions.z; - percentChange = changeInDimensions.z / initialDimensions.z; + var mouseXDifference = (event.x - beginMouseEvent.x) / viewportDimensions.x; + var mouseYDifference = (beginMouseEvent.y - event.y) / viewportDimensions.y; + var mouseDifference = mouseXDifference + mouseYDifference; + dimensionChange = mouseDifference * dimensionsMultiple; } + + var averageInitialDimension = (initialDimensions.x + initialDimensions.y + initialDimensions.z) / 3; + percentChange = dimensionChange / averageInitialDimension; percentChange += 1.0; newDimensions = Vec3.multiply(percentChange, initialDimensions); + newDimensions.x = Math.abs(newDimensions.x); + newDimensions.y = Math.abs(newDimensions.y); + newDimensions.z = Math.abs(newDimensions.z); } else { newDimensions = Vec3.sum(initialDimensions, changeInDimensions); } - + var minimumDimension = directionEnum === STRETCH_DIRECTION.ALL ? STRETCH_ALL_MINIMUM_DIMENSION : STRETCH_MINIMUM_DIMENSION; if (newDimensions.x < minimumDimension) { @@ -2393,9 +2358,9 @@ SelectionDisplay = (function() { newDimensions.z = minimumDimension; changeInDimensions.z = minimumDimension - initialDimensions.z; } - + var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); - if (directionEnum === STRETCH_DIRECTION.ALL) { + if (proportional) { changeInPosition = { x: 0, y: 0, z: 0 }; } var newPosition = Vec3.sum(initialPosition, changeInPosition); @@ -2429,52 +2394,30 @@ SelectionDisplay = (function() { } function addHandleStretchTool(overlay, mode, directionEnum) { - var directionVector, offset, stretchPanel; + var directionVector, offset, stretchPanel, handleStretchCube; if (directionEnum === STRETCH_DIRECTION.X) { stretchPanel = handleStretchXPanel; + handleStretchCube = handleStretchXCube; directionVector = { x: -1, y: 0, z: 0 }; } else if (directionEnum === STRETCH_DIRECTION.Y) { stretchPanel = handleStretchYPanel; + handleStretchCube = handleStretchYCube; directionVector = { x: 0, y: -1, z: 0 }; } else if (directionEnum === STRETCH_DIRECTION.Z) { stretchPanel = handleStretchZPanel; + handleStretchCube = handleStretchZCube; directionVector = { x: 0, y: 0, z: -1 }; } offset = Vec3.multiply(directionVector, NEGATE_VECTOR); - var tool = makeStretchTool(mode, directionEnum, directionVector, directionVector, offset, stretchPanel, null); + var tool = makeStretchTool(mode, directionEnum, directionVector, directionVector, offset, stretchPanel, handleStretchCube); return addHandleTool(overlay, tool); } // TOOL DEFINITION: HANDLE SCALE TOOL - function addHandleScaleTool(overlay, mode, directionEnum) { - var directionVector, offset, selectedHandle; - if (directionEnum === SCALE_DIRECTION.LBN) { - directionVector = { x: 1, y: 1, z: 1 }; - selectedHandle = handleScaleLBNCube; - } else if (directionEnum === SCALE_DIRECTION.RBN) { - directionVector = { x: -1, y: 1, z: 1 }; - selectedHandle = handleScaleRBNCube; - } else if (directionEnum === SCALE_DIRECTION.LBF) { - directionVector = { x: 1, y: 1, z: -1 }; - selectedHandle = handleScaleLBFCube; - } else if (directionEnum === SCALE_DIRECTION.RBF) { - directionVector = { x: -1, y: 1, z: -1 }; - selectedHandle = handleScaleRBFCube; - } else if (directionEnum === SCALE_DIRECTION.LTN) { - directionVector = { x: 1, y: -1, z: 1 }; - selectedHandle = handleScaleLTNCube; - } else if (directionEnum === SCALE_DIRECTION.RTN) { - directionVector = { x: -1, y: -1, z: 1 }; - selectedHandle = handleScaleRTNCube; - } else if (directionEnum === SCALE_DIRECTION.LTF) { - directionVector = { x: 1, y: -1, z: -1 }; - selectedHandle = handleScaleLTFCube; - } else if (directionEnum === SCALE_DIRECTION.RTF) { - directionVector = { x: -1, y: -1, z: -1 }; - selectedHandle = handleScaleRTFCube; - } - offset = Vec3.multiply(directionVector, NEGATE_VECTOR); - var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVector, directionVector, offset, null, selectedHandle); + function addHandleScaleTool(overlay, mode) { + var directionVector = { x:0, y:0, z:0 }; + var offset = { x:0, y:0, z:0 }; + var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVector, directionVector, offset, null, handleScaleCube); return addHandleTool(overlay, tool); } @@ -2747,18 +2690,11 @@ SelectionDisplay = (function() { addHandleRotateTool(handleRotateYawRing, "ROTATE_YAW", ROTATE_DIRECTION.YAW); addHandleRotateTool(handleRotateRollRing, "ROTATE_ROLL", ROTATE_DIRECTION.ROLL); - addHandleStretchTool(handleStretchXSphere, "STRETCH_X", STRETCH_DIRECTION.X); - addHandleStretchTool(handleStretchYSphere, "STRETCH_Y", STRETCH_DIRECTION.Y); - addHandleStretchTool(handleStretchZSphere, "STRETCH_Z", STRETCH_DIRECTION.Z); - - addHandleScaleTool(handleScaleLBNCube, "SCALE_LBN", SCALE_DIRECTION.LBN); - addHandleScaleTool(handleScaleRBNCube, "SCALE_RBN", SCALE_DIRECTION.RBN); - addHandleScaleTool(handleScaleLBFCube, "SCALE_LBF", SCALE_DIRECTION.LBF); - addHandleScaleTool(handleScaleRBFCube, "SCALE_RBF", SCALE_DIRECTION.RBF); - addHandleScaleTool(handleScaleLTNCube, "SCALE_LTN", SCALE_DIRECTION.LTN); - addHandleScaleTool(handleScaleRTNCube, "SCALE_RTN", SCALE_DIRECTION.RTN); - addHandleScaleTool(handleScaleLTFCube, "SCALE_LTF", SCALE_DIRECTION.LTF); - addHandleScaleTool(handleScaleRTFCube, "SCALE_RTF", SCALE_DIRECTION.RTF); + addHandleStretchTool(handleStretchXCube, "STRETCH_X", STRETCH_DIRECTION.X); + addHandleStretchTool(handleStretchYCube, "STRETCH_Y", STRETCH_DIRECTION.Y); + addHandleStretchTool(handleStretchZCube, "STRETCH_Z", STRETCH_DIRECTION.Z); + addHandleScaleTool(handleScaleCube, "SCALE"); + return that; }()); diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js index 3a114f23c7..6f62742e8f 100644 --- a/scripts/system/libraries/gridTool.js +++ b/scripts/system/libraries/gridTool.js @@ -267,7 +267,6 @@ GridTool = function(opts) { try { data = JSON.parse(data); } catch (e) { - print("gridTool.js: Error parsing JSON: " + e.name + " data " + data); return; } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 7b4f05193f..13ad1f6b69 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -16,1147 +16,1082 @@ var selectionDisplay = null; // for gridTool.js to ignore (function () { // BEGIN LOCAL_SCOPE +var AppUi = Script.require('appUi'); +Script.include("/~/system/libraries/gridTool.js"); +Script.include("/~/system/libraries/connectionUtils.js"); - Script.include("/~/system/libraries/WebTablet.js"); - Script.include("/~/system/libraries/gridTool.js"); - Script.include("/~/system/libraries/connectionUtils.js"); +var METAVERSE_SERVER_URL = Account.metaverseServerURL; +var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); +var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); +var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; +var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; +var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; +var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; +var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav")); - var METAVERSE_SERVER_URL = Account.metaverseServerURL; - var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; - var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. - var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); - var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); - var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; - var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; - var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; - var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml"; - var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav")); +// Event bridge messages. +var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; +var CLARA_IO_STATUS = "CLARA.IO STATUS"; +var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; +var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; +var GOTO_DIRECTORY = "GOTO_DIRECTORY"; +var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; +var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; +var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; - var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; - // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; +var CLARA_DOWNLOAD_TITLE = "Preparing Download"; +var messageBox = null; +var isDownloadBeingCancelled = false; - // Event bridge messages. - var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; - var CLARA_IO_STATUS = "CLARA.IO STATUS"; - var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; - var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; - var GOTO_DIRECTORY = "GOTO_DIRECTORY"; - var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; - var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; - var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; +var CANCEL_BUTTON = 4194304; // QMessageBox::Cancel +var NO_BUTTON = 0; // QMessageBox::NoButton - var CLARA_DOWNLOAD_TITLE = "Preparing Download"; - var messageBox = null; - var isDownloadBeingCancelled = false; +var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server."; - var CANCEL_BUTTON = 4194304; // QMessageBox::Cancel - var NO_BUTTON = 0; // QMessageBox::NoButton - - var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server."; - - function onMessageBoxClosed(id, button) { - if (id === messageBox && button === CANCEL_BUTTON) { - isDownloadBeingCancelled = true; - messageBox = null; - tablet.emitScriptEvent(CLARA_IO_CANCEL_DOWNLOAD); - } - } - - var onMarketplaceScreen = false; - var onCommerceScreen = false; - - var debugCheckout = false; - var debugError = false; - function showMarketplace() { - if (!debugCheckout) { - UserActivityLogger.openedMarketplace(); - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - } else { - tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); - sendToQml({ - method: 'updateCheckoutQML', params: { - itemId: '424611a2-73d0-4c03-9087-26a6a279257b', - itemName: '2018-02-15 Finnegon', - itemPrice: (debugError ? 10 : 3), - itemHref: 'http://devmpassets.highfidelity.com/424611a2-73d0-4c03-9087-26a6a279257b-v1/finnigon.fst', - categories: ["Miscellaneous"] - } - }); - } - } - - function messagesWaiting(isWaiting) { - if (marketplaceButton) { - marketplaceButton.editProperties({ - icon: (isWaiting ? WAITING_ICON : NORMAL_ICON), - activeIcon: (isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE) - }); - } - } - - function onCanWriteAssetsChanged() { - var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets(); - tablet.emitScriptEvent(message); - } - - - var tabletShouldBeVisibleInSecondaryCamera = false; - function setTabletVisibleInSecondaryCamera(visibleInSecondaryCam) { - if (visibleInSecondaryCam) { - // if we're potentially showing the tablet, only do so if it was visible before - if (!tabletShouldBeVisibleInSecondaryCamera) { - return; - } - } else { - // if we're hiding the tablet, check to see if it was visible in the first place - tabletShouldBeVisibleInSecondaryCamera = Overlays.getProperty(HMD.tabletID, "isVisibleInSecondaryCamera"); - } - - Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); - Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); - Overlays.editOverlay(HMD.homeButtonHighlightID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); - Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); - } - - function openWallet() { - tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); - } - - function setCertificateInfo(currentEntityWithContextOverlay, itemCertificateId) { - wireEventBridge(true); - var certificateId = itemCertificateId || (Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID); - sendToQml({ - method: 'inspectionCertificate_setCertificateId', - entityId: currentEntityWithContextOverlay, - certificateId: certificateId +function onMessageBoxClosed(id, button) { + if (id === messageBox && button === CANCEL_BUTTON) { + isDownloadBeingCancelled = true; + messageBox = null; + ui.sendToHtml({ + type: CLARA_IO_CANCEL_DOWNLOAD }); } +} - function onUsernameChanged() { - if (onMarketplaceScreen) { - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - } - } +function onCanWriteAssetsChanged() { + ui.sendToHtml({ + type: CAN_WRITE_ASSETS, + canWriteAssets: Entities.canWriteAssets() + }); +} - var userHasUpdates = false; - function sendCommerceSettings() { - tablet.emitScriptEvent(JSON.stringify({ - type: "marketplaces", - action: "commerceSetting", - data: { - commerceMode: Settings.getValue("commerce", true), - userIsLoggedIn: Account.loggedIn, - walletNeedsSetup: Wallet.walletStatus === 1, - metaverseServerURL: Account.metaverseServerURL, - messagesWaiting: userHasUpdates - } - })); - } - // BEGIN AVATAR SELECTOR LOGIC - var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; - var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; - var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; - - var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. - - function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. - overlays[key] = this; - this.key = key; - this.selected = false; - this.hovering = false; - this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... - } - // Instance methods: - ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay - Overlays.deleteOverlay(this.activeOverlay); - delete overlays[this.key]; - }; - - ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay - Overlays.editOverlay(this.activeOverlay, properties); - }; - - function color(selected, hovering) { - var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; - function scale(component) { - var delta = 0xFF - component; - return component; - } - return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; - } - // so we don't have to traverse the overlays to get the last one - var lastHoveringId = 0; - ExtendedOverlay.prototype.hover = function (hovering) { - this.hovering = hovering; - if (this.key === lastHoveringId) { - if (hovering) { - return; - } - lastHoveringId = 0; - } - this.editOverlay({ color: color(this.selected, hovering) }); - if (hovering) { - // un-hover the last hovering overlay - if (lastHoveringId && lastHoveringId !== this.key) { - ExtendedOverlay.get(lastHoveringId).hover(false); - } - lastHoveringId = this.key; - } - }; - ExtendedOverlay.prototype.select = function (selected) { - if (this.selected === selected) { +var tabletShouldBeVisibleInSecondaryCamera = false; +function setTabletVisibleInSecondaryCamera(visibleInSecondaryCam) { + if (visibleInSecondaryCam) { + // if we're potentially showing the tablet, only do so if it was visible before + if (!tabletShouldBeVisibleInSecondaryCamera) { return; } + } else { + // if we're hiding the tablet, check to see if it was visible in the first place + tabletShouldBeVisibleInSecondaryCamera = Overlays.getProperty(HMD.tabletID, "isVisibleInSecondaryCamera"); + } - this.editOverlay({ color: color(selected, this.hovering) }); - this.selected = selected; - }; - // Class methods: - var selectedId = false; - ExtendedOverlay.isSelected = function (id) { - return selectedId === id; - }; - ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier - return overlays[key]; - }; - ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. - var key; - for (key in overlays) { - if (iterator(ExtendedOverlay.get(key))) { - return; - } + Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.homeButtonHighlightID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); +} + +function openWallet() { + ui.open(MARKETPLACE_WALLET_QML_PATH); +} + +// Function Name: wireQmlEventBridge() +// +// Description: +// -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or +// disable to event bridge. +// +// Relevant Variables: +// -hasEventBridge: true/false depending on whether we've already connected the event bridge. +var hasEventBridge = false; +function wireQmlEventBridge(on) { + if (!ui.tablet) { + print("Warning in wireQmlEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + ui.tablet.fromQml.connect(onQmlMessageReceived); + hasEventBridge = true; } - }; - ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) - if (lastHoveringId) { + } else { + if (hasEventBridge) { + ui.tablet.fromQml.disconnect(onQmlMessageReceived); + hasEventBridge = false; + } + } +} + +var contextOverlayEntity = ""; +function openInspectionCertificateQML(currentEntityWithContextOverlay) { + ui.open(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH); + contextOverlayEntity = currentEntityWithContextOverlay; +} + +function setCertificateInfo(currentEntityWithContextOverlay, itemCertificateId) { + var certificateId = itemCertificateId || + (Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID); + ui.tablet.sendToQml({ + method: 'inspectionCertificate_setCertificateId', + entityId: currentEntityWithContextOverlay, + certificateId: certificateId + }); +} + +function onUsernameChanged() { + if (onMarketplaceScreen) { + ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + } +} + +var userHasUpdates = false; +function sendCommerceSettings() { + ui.sendToHtml({ + type: "marketplaces", + action: "commerceSetting", + data: { + commerceMode: Settings.getValue("commerce", true), + userIsLoggedIn: Account.loggedIn, + walletNeedsSetup: Wallet.walletStatus === 1, + metaverseServerURL: Account.metaverseServerURL, + messagesWaiting: userHasUpdates + } + }); +} + +// BEGIN AVATAR SELECTOR LOGIC +var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; +var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; +var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; + +var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. + +function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. + overlays[key] = this; + this.key = key; + this.selected = false; + this.hovering = false; + this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... +} +// Instance methods: +ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay + Overlays.deleteOverlay(this.activeOverlay); + delete overlays[this.key]; +}; + +ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay + Overlays.editOverlay(this.activeOverlay, properties); +}; + +function color(selected, hovering) { + var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; + function scale(component) { + return component; + } + return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; +} +// so we don't have to traverse the overlays to get the last one +var lastHoveringId = 0; +ExtendedOverlay.prototype.hover = function (hovering) { + this.hovering = hovering; + if (this.key === lastHoveringId) { + if (hovering) { + return; + } + lastHoveringId = 0; + } + this.editOverlay({ color: color(this.selected, hovering) }); + if (hovering) { + // un-hover the last hovering overlay + if (lastHoveringId && lastHoveringId !== this.key) { ExtendedOverlay.get(lastHoveringId).hover(false); } - }; + lastHoveringId = this.key; + } +}; +ExtendedOverlay.prototype.select = function (selected) { + if (this.selected === selected) { + return; + } - // hit(overlay) on the one overlay intersected by pickRay, if any. - // noHit() if no ExtendedOverlay was intersected (helps with hover) - ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { - var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones. - if (!pickedOverlay.intersects) { - if (noHit) { - return noHit(); - } + this.editOverlay({ color: color(selected, this.hovering) }); + this.selected = selected; +}; +// Class methods: +var selectedId = false; +ExtendedOverlay.isSelected = function (id) { + return selectedId === id; +}; +ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier + return overlays[key]; +}; +ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. + var key; + for (key in overlays) { + if (iterator(ExtendedOverlay.get(key))) { return; } - ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. - if ((overlay.activeOverlay) === pickedOverlay.overlayID) { - hit(overlay); - return true; - } - }); - }; - - function addAvatarNode(id) { - return new ExtendedOverlay(id, "sphere", { - drawInFront: true, - solid: true, - alpha: 0.8, - color: color(false, false), - ignoreRayIntersection: false - }); } - - var pingPong = true; - function updateOverlays() { - var eye = Camera.position; - AvatarList.getAvatarIdentifiers().forEach(function (id) { - if (!id) { - return; // don't update ourself, or avatars we're not interested in - } - var avatar = AvatarList.getAvatar(id); - if (!avatar) { - return; // will be deleted below if there had been an overlay. - } - var overlay = ExtendedOverlay.get(id); - if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. - overlay = addAvatarNode(id); - } - var target = avatar.position; - var distance = Vec3.distance(target, eye); - var offset = 0.2; - var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position) - var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can - if (headIndex > 0) { - offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; - } - - // move a bit in front, towards the camera - target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); - - // now bump it up a bit - target.y = target.y + offset; - - overlay.ping = pingPong; - overlay.editOverlay({ - color: color(ExtendedOverlay.isSelected(id), overlay.hovering), - position: target, - dimensions: 0.032 * distance - }); - }); - pingPong = !pingPong; - ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) - if (overlay.ping === pingPong) { - overlay.deleteOverlay(); - } - }); - } - function removeOverlays() { - selectedId = false; - lastHoveringId = 0; - ExtendedOverlay.some(function (overlay) { - overlay.deleteOverlay(); - }); +}; +ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) + if (lastHoveringId) { + ExtendedOverlay.get(lastHoveringId).hover(false); } +}; - // - // Clicks. - // - function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { - if (selectedId === id) { - var message = { - method: 'updateSelectedRecipientUsername', - userName: username === "" ? "unknown username" : username - }; - sendToQml(message); +// hit(overlay) on the one overlay intersected by pickRay, if any. +// noHit() if no ExtendedOverlay was intersected (helps with hover) +ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { + var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones. + if (!pickedOverlay.intersects) { + if (noHit) { + return noHit(); } + return; } - function handleClick(pickRay) { - ExtendedOverlay.applyPickRay(pickRay, function (overlay) { - var nextSelectedStatus = !overlay.selected; - var avatarId = overlay.key; - selectedId = nextSelectedStatus ? avatarId : false; - if (nextSelectedStatus) { - Users.requestUsernameFromID(avatarId); - } - var message = { - method: 'selectRecipient', - id: avatarId, - isSelected: nextSelectedStatus, - displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', - userName: '' - }; - sendToQml(message); - - ExtendedOverlay.some(function (overlay) { - var id = overlay.key; - var selected = ExtendedOverlay.isSelected(id); - overlay.select(selected); - }); - + ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. + if ((overlay.activeOverlay) === pickedOverlay.overlayID) { + hit(overlay); return true; - }); - } - function handleMouseEvent(mousePressEvent) { // handleClick if we get one. - if (!mousePressEvent.isLeftButton) { - return; } - handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); + }); +}; + +function addAvatarNode(id) { + return new ExtendedOverlay(id, "sphere", { + drawInFront: true, + solid: true, + alpha: 0.8, + color: color(false, false), + ignoreRayIntersection: false + }); +} + +var pingPong = true; +function updateOverlays() { + var eye = Camera.position; + AvatarList.getAvatarIdentifiers().forEach(function (id) { + if (!id) { + return; // don't update ourself, or avatars we're not interested in + } + var avatar = AvatarList.getAvatar(id); + if (!avatar) { + return; // will be deleted below if there had been an overlay. + } + var overlay = ExtendedOverlay.get(id); + if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. + overlay = addAvatarNode(id); + } + var target = avatar.position; + var distance = Vec3.distance(target, eye); + var offset = 0.2; + var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position) + var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can + if (headIndex > 0) { + offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; + } + + // move a bit in front, towards the camera + target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); + + // now bump it up a bit + target.y = target.y + offset; + + overlay.ping = pingPong; + overlay.editOverlay({ + color: color(ExtendedOverlay.isSelected(id), overlay.hovering), + position: target, + dimensions: 0.032 * distance + }); + }); + pingPong = !pingPong; + ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) + if (overlay.ping === pingPong) { + overlay.deleteOverlay(); + } + }); +} +function removeOverlays() { + selectedId = false; + lastHoveringId = 0; + ExtendedOverlay.some(function (overlay) { + overlay.deleteOverlay(); + }); +} + +// +// Clicks. +// +function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { + if (selectedId === id) { + var message = { + method: 'updateSelectedRecipientUsername', + userName: username === "" ? "unknown username" : username + }; + ui.tablet.sendToQml(message); } - function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic - ExtendedOverlay.applyPickRay(pickRay, function (overlay) { - overlay.hover(true); - }, function () { +} +function handleClick(pickRay) { + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + var nextSelectedStatus = !overlay.selected; + var avatarId = overlay.key; + selectedId = nextSelectedStatus ? avatarId : false; + if (nextSelectedStatus) { + Users.requestUsernameFromID(avatarId); + } + var message = { + method: 'selectRecipient', + id: avatarId, + isSelected: nextSelectedStatus, + displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', + userName: '' + }; + ui.tablet.sendToQml(message); + + ExtendedOverlay.some(function (overlay) { + var id = overlay.key; + var selected = ExtendedOverlay.isSelected(id); + overlay.select(selected); + }); + + return true; + }); +} +function handleMouseEvent(mousePressEvent) { // handleClick if we get one. + if (!mousePressEvent.isLeftButton) { + return; + } + handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); +} +function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + overlay.hover(true); + }, function () { + ExtendedOverlay.unHover(); + }); +} + +// handy global to keep track of which hand is the mouse (if any) +var currentHandPressed = 0; +var TRIGGER_CLICK_THRESHOLD = 0.85; +var TRIGGER_PRESS_THRESHOLD = 0.05; + +function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position + var pickRay; + if (HMD.active) { + if (currentHandPressed !== 0) { + pickRay = controllerComputePickRay(currentHandPressed); + } else { + // nothing should hover, so ExtendedOverlay.unHover(); - }); - } - - // handy global to keep track of which hand is the mouse (if any) - var currentHandPressed = 0; - var TRIGGER_CLICK_THRESHOLD = 0.85; - var TRIGGER_PRESS_THRESHOLD = 0.05; - - function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position - var pickRay; - if (HMD.active) { - if (currentHandPressed !== 0) { - pickRay = controllerComputePickRay(currentHandPressed); - } else { - // nothing should hover, so - ExtendedOverlay.unHover(); - return; - } - } else { - pickRay = Camera.computePickRay(event.x, event.y); - } - handleMouseMove(pickRay); - } - function handleTriggerPressed(hand, value) { - // The idea is if you press one trigger, it is the one - // we will consider the mouse. Even if the other is pressed, - // we ignore it until this one is no longer pressed. - var isPressed = value > TRIGGER_PRESS_THRESHOLD; - if (currentHandPressed === 0) { - currentHandPressed = isPressed ? hand : 0; return; } - if (currentHandPressed === hand) { - currentHandPressed = isPressed ? hand : 0; - return; - } - // otherwise, the other hand is still triggered - // so do nothing. + } else { + pickRay = Camera.computePickRay(event.x, event.y); } - - // We get mouseMoveEvents from the handControllers, via handControllerPointer. - // But we don't get mousePressEvents. - var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); - var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); - function controllerComputePickRay(hand) { - var controllerPose = getControllerWorldLocation(hand, true); - if (controllerPose.valid) { - return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; - } + handleMouseMove(pickRay); +} +function handleTriggerPressed(hand, value) { + // The idea is if you press one trigger, it is the one + // we will consider the mouse. Even if the other is pressed, + // we ignore it until this one is no longer pressed. + var isPressed = value > TRIGGER_PRESS_THRESHOLD; + if (currentHandPressed === 0) { + currentHandPressed = isPressed ? hand : 0; + return; } - function makeClickHandler(hand) { - return function (clicked) { - if (clicked > TRIGGER_CLICK_THRESHOLD) { - var pickRay = controllerComputePickRay(hand); - handleClick(pickRay); - } - }; + if (currentHandPressed === hand) { + currentHandPressed = isPressed ? hand : 0; + return; } - function makePressHandler(hand) { - return function (value) { - handleTriggerPressed(hand, value); - }; + // otherwise, the other hand is still triggered + // so do nothing. +} + +// We get mouseMoveEvents from the handControllers, via handControllerPointer. +// But we don't get mousePressEvents. +var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); +var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); +function controllerComputePickRay(hand) { + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { + return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; } - triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); - triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); - triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); - triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); - // END AVATAR SELECTOR LOGIC - - var grid = new Grid(); - function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { - // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original - // position in the given direction. - var CORNERS = [ - { x: 0, y: 0, z: 0 }, - { x: 0, y: 0, z: 1 }, - { x: 0, y: 1, z: 0 }, - { x: 0, y: 1, z: 1 }, - { x: 1, y: 0, z: 0 }, - { x: 1, y: 0, z: 1 }, - { x: 1, y: 1, z: 0 }, - { x: 1, y: 1, z: 1 }, - ]; - - // Go through all corners and find least (most negative) distance in front of position. - var distance = 0; - for (var i = 0, length = CORNERS.length; i < length; i++) { - var cornerVector = - Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); - var cornerDistance = Vec3.dot(cornerVector, direction); - distance = Math.min(cornerDistance, distance); +} +function makeClickHandler(hand) { + return function (clicked) { + if (clicked > TRIGGER_CLICK_THRESHOLD) { + var pickRay = controllerComputePickRay(hand); + handleClick(pickRay); } - position = Vec3.sum(Vec3.multiply(distance, direction), position); - return position; - } - - var HALF_TREE_SCALE = 16384; - function getPositionToCreateEntity(extra) { - var CREATE_DISTANCE = 2; - var position; - var delta = extra !== undefined ? extra : 0; - if (Camera.mode === "entity" || Camera.mode === "independent") { - position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); - } else { - position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); - position.y += 0.5; - } - - if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { - return null; - } - return position; - } - - function rezEntity(itemHref, itemType) { - var isWearable = itemType === "wearable"; - var success = Clipboard.importEntities(itemHref); - var wearableLocalPosition = null; - var wearableLocalRotation = null; - var wearableLocalDimensions = null; - var wearableDimensions = null; - - if (itemType === "contentSet") { - console.log("Item is a content set; codepath shouldn't go here."); - return; - } - - if (isWearable) { - var wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.transforms"); - if (!wearableTransforms) { - // TODO delete this clause - wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.tranforms"); - } - var certPos = itemHref.search("certificate_id="); // TODO how do I parse a URL from here? - if (certPos >= 0) { - certPos += 15; // length of "certificate_id=" - var certURLEncoded = itemHref.substring(certPos); - var certB64Encoded = decodeURIComponent(certURLEncoded); - for (var key in wearableTransforms) { - if (wearableTransforms.hasOwnProperty(key)) { - var certificateTransforms = wearableTransforms[key].certificateTransforms; - if (certificateTransforms) { - for (var certID in certificateTransforms) { - if (certificateTransforms.hasOwnProperty(certID) && - certID == certB64Encoded) { - var certificateTransform = certificateTransforms[certID]; - wearableLocalPosition = certificateTransform.localPosition; - wearableLocalRotation = certificateTransform.localRotation; - wearableLocalDimensions = certificateTransform.localDimensions; - wearableDimensions = certificateTransform.dimensions; - } - } - } - } - } - } - } - - if (success) { - var VERY_LARGE = 10000; - var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE; - var position = Vec3.ZERO; - if (!isLargeImport) { - position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2); - } - if (position !== null && position !== undefined) { - var pastedEntityIDs = Clipboard.pasteEntities(position); - if (!isLargeImport) { - // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move - // entities after they're imported so that they're all the correct distance in front of and with geometric mean - // centered on the avatar/camera direction. - var deltaPosition = Vec3.ZERO; - var entityPositions = []; - var entityParentIDs = []; - - var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type; - var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; - if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) { - var targetDirection; - if (Camera.mode === "entity" || Camera.mode === "independent") { - targetDirection = Camera.orientation; - } else { - targetDirection = MyAvatar.orientation; - } - targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); - - var targetPosition = getPositionToCreateEntity(); - var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. - var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. - for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { - var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", - "registrationPoint", "rotation", "parentID"]); - var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, - curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation); - var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position); - var distance = Vec3.dot(delta, targetDirection); - deltaParallel = Math.min(distance, deltaParallel); - deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), - deltaPerpendicular); - entityPositions[i] = curLoopEntityProps.position; - entityParentIDs[i] = curLoopEntityProps.parentID; - } - deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); - deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); - } - - if (grid.getSnapToGrid()) { - var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", - "registrationPoint"]); - var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position); - position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions, - firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint); - deltaPosition = Vec3.subtract(position, firstEntityProps.position); - } - - if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { - for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { - if (Uuid.isNull(entityParentIDs[editEntityIndex])) { - Entities.editEntity(pastedEntityIDs[editEntityIndex], { - position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex]) - }); - } - } - } - } - - if (isWearable) { - // apply the relative offsets saved during checkout - var offsets = {}; - if (wearableLocalPosition) { - offsets.localPosition = wearableLocalPosition; - } - if (wearableLocalRotation) { - offsets.localRotation = wearableLocalRotation; - } - if (wearableLocalDimensions) { - offsets.localDimensions = wearableLocalDimensions; - } else if (wearableDimensions) { - offsets.dimensions = wearableDimensions; - } - // we currently assume a wearable is a single entity - Entities.editEntity(pastedEntityIDs[0], offsets); - } - - var rezPosition = Entities.getEntityProperties(pastedEntityIDs[0], "position").position; - - Audio.playSound(REZZING_SOUND, { - volume: 1.0, - position: rezPosition, - localOnly: true - }); - - } else { - Window.notifyEditError("Can't import entities: entities would be out of bounds."); - } - } else { - Window.notifyEditError("There was an error importing the entity file."); - } - } - - var referrerURL; // Used for updating Purchases QML - var filterText; // Used for updating Purchases QML - function onMessage(message) { - if (message === GOTO_DIRECTORY) { - tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); - } else if (message === QUERY_CAN_WRITE_ASSETS) { - tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); - } else if (message === WARN_USER_NO_PERMISSIONS) { - Window.alert(NO_PERMISSIONS_ERROR_MESSAGE); - } else if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) { - if (isDownloadBeingCancelled) { - return; - } - - var text = message.slice(CLARA_IO_STATUS.length); - if (messageBox === null) { - messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); - } else { - Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); - } - return; - } else if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) { - if (messageBox !== null) { - Window.closeMessageBox(messageBox); - messageBox = null; - } - return; - } else if (message === CLARA_IO_CANCELLED_DOWNLOAD) { - isDownloadBeingCancelled = false; - } else { - var parsedJsonMessage = JSON.parse(message); - if (parsedJsonMessage.type === "CHECKOUT") { - wireEventBridge(true); - tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); - sendToQml({ - method: 'updateCheckoutQML', - params: parsedJsonMessage - }); - } else if (parsedJsonMessage.type === "REQUEST_SETTING") { - sendCommerceSettings(); - } else if (parsedJsonMessage.type === "PURCHASES") { - referrerURL = parsedJsonMessage.referrerURL; - filterText = ""; - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); - } else if (parsedJsonMessage.type === "LOGIN") { - openLoginWindow(); - } else if (parsedJsonMessage.type === "WALLET_SETUP") { - wireEventBridge(true); - sendToQml({ - method: 'updateWalletReferrer', - referrer: "marketplace cta" - }); - openWallet(); - } else if (parsedJsonMessage.type === "MY_ITEMS") { - referrerURL = MARKETPLACE_URL_INITIAL; - filterText = ""; - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); - wireEventBridge(true); - sendToQml({ - method: 'purchases_showMyItems' - }); - } - } - } - - function onButtonClicked() { - if (!tablet) { - print("Warning in buttonClicked(): 'tablet' undefined!"); - return; - } - if (onMarketplaceScreen || onCommerceScreen) { - // for toolbar-mode: go back to home screen, this will close the window. - tablet.gotoHomeScreen(); - } else { - if (HMD.tabletID) { - Entities.editEntity(HMD.tabletID, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); - } - showMarketplace(); - } - } - - // Function Name: sendToQml() - // - // Description: - // -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to - // the QML in the format "{method, params}", like json-rpc. See also fromQml(). - function sendToQml(message) { - tablet.sendToQml(message); - } - - var sendAssetRecipient; - var sendAssetParticleEffectUpdateTimer; - var particleEffectTimestamp; - var sendAssetParticleEffect; - var SEND_ASSET_PARTICLE_TIMER_UPDATE = 250; - var SEND_ASSET_PARTICLE_EMITTING_DURATION = 3000; - var SEND_ASSET_PARTICLE_LIFETIME_SECONDS = 8; - var SEND_ASSET_PARTICLE_PROPERTIES = { - accelerationSpread: { x: 0, y: 0, z: 0 }, - alpha: 1, - alphaFinish: 1, - alphaSpread: 0, - alphaStart: 1, - azimuthFinish: 0, - azimuthStart: -6, - color: { red: 255, green: 222, blue: 255 }, - colorFinish: { red: 255, green: 229, blue: 225 }, - colorSpread: { red: 0, green: 0, blue: 0 }, - colorStart: { red: 243, green: 255, blue: 255 }, - emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate - emitDimensions: { x: 0, y: 0, z: 0 }, - emitOrientation: { x: 0, y: 0, z: 0 }, - emitRate: 4, - emitSpeed: 2.1, - emitterShouldTrail: true, - isEmitting: 1, - lifespan: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate - lifetime: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, - maxParticles: 20, - name: 'asset-particles', - particleRadius: 0.2, - polarFinish: 0, - polarStart: 0, - radiusFinish: 0.05, - radiusSpread: 0, - radiusStart: 0.2, - speedSpread: 0, - textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png", - type: 'ParticleEffect' }; +} +function makePressHandler(hand) { + return function (value) { + handleTriggerPressed(hand, value); + }; +} +triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); +triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); +triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); +triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); +// END AVATAR SELECTOR LOGIC - function updateSendAssetParticleEffect() { - var timestampNow = Date.now(); - if ((timestampNow - particleEffectTimestamp) > (SEND_ASSET_PARTICLE_LIFETIME_SECONDS * 1000)) { - deleteSendAssetParticleEffect(); - return; - } else if ((timestampNow - particleEffectTimestamp) > SEND_ASSET_PARTICLE_EMITTING_DURATION) { - Entities.editEntity(sendAssetParticleEffect, { - isEmitting: 0 - }); - } else if (sendAssetParticleEffect) { - var recipientPosition = AvatarList.getAvatar(sendAssetRecipient).position; - var distance = Vec3.distance(recipientPosition, MyAvatar.position); - var accel = Vec3.subtract(recipientPosition, MyAvatar.position); - accel.y -= 3.0; - var life = Math.sqrt(2 * distance / Vec3.length(accel)); - Entities.editEntity(sendAssetParticleEffect, { - emitAcceleration: accel, - lifespan: life +var grid = new Grid(); +function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { + // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original + // position in the given direction. + var CORNERS = [ + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: 1 }, + { x: 0, y: 1, z: 0 }, + { x: 0, y: 1, z: 1 }, + { x: 1, y: 0, z: 0 }, + { x: 1, y: 0, z: 1 }, + { x: 1, y: 1, z: 0 }, + { x: 1, y: 1, z: 1 } + ]; + + // Go through all corners and find least (most negative) distance in front of position. + var distance = 0; + for (var i = 0, length = CORNERS.length; i < length; i++) { + var cornerVector = + Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); + var cornerDistance = Vec3.dot(cornerVector, direction); + distance = Math.min(cornerDistance, distance); + } + position = Vec3.sum(Vec3.multiply(distance, direction), position); + return position; +} + +var HALF_TREE_SCALE = 16384; +function getPositionToCreateEntity(extra) { + var CREATE_DISTANCE = 2; + var position; + var delta = extra !== undefined ? extra : 0; + if (Camera.mode === "entity" || Camera.mode === "independent") { + position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); + } else { + position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); + position.y += 0.5; + } + + if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { + return null; + } + return position; +} + +function rezEntity(itemHref, itemType) { + var isWearable = itemType === "wearable"; + var success = Clipboard.importEntities(itemHref); + var wearableLocalPosition = null; + var wearableLocalRotation = null; + var wearableLocalDimensions = null; + var wearableDimensions = null; + + if (itemType === "contentSet") { + console.log("Item is a content set; codepath shouldn't go here."); + return; + } + + if (isWearable) { + var wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.transforms"); + if (!wearableTransforms) { + // TODO delete this clause + wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.tranforms"); + } + var certPos = itemHref.search("certificate_id="); // TODO how do I parse a URL from here? + if (certPos >= 0) { + certPos += 15; // length of "certificate_id=" + var certURLEncoded = itemHref.substring(certPos); + var certB64Encoded = decodeURIComponent(certURLEncoded); + for (var key in wearableTransforms) { + if (wearableTransforms.hasOwnProperty(key)) { + var certificateTransforms = wearableTransforms[key].certificateTransforms; + if (certificateTransforms) { + for (var certID in certificateTransforms) { + if (certificateTransforms.hasOwnProperty(certID) && + certID == certB64Encoded) { + var certificateTransform = certificateTransforms[certID]; + wearableLocalPosition = certificateTransform.localPosition; + wearableLocalRotation = certificateTransform.localRotation; + wearableLocalDimensions = certificateTransform.localDimensions; + wearableDimensions = certificateTransform.dimensions; + } + } + } + } + } + } + } + + if (success) { + var VERY_LARGE = 10000; + var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE; + var position = Vec3.ZERO; + if (!isLargeImport) { + position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2); + } + if (position !== null && position !== undefined) { + var pastedEntityIDs = Clipboard.pasteEntities(position); + if (!isLargeImport) { + // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move + // entities after they're imported so that they're all the correct distance in front of and with geometric mean + // centered on the avatar/camera direction. + var deltaPosition = Vec3.ZERO; + var entityPositions = []; + var entityParentIDs = []; + + var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type; + var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; + if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) { + var targetDirection; + if (Camera.mode === "entity" || Camera.mode === "independent") { + targetDirection = Camera.orientation; + } else { + targetDirection = MyAvatar.orientation; + } + targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); + + var targetPosition = getPositionToCreateEntity(); + var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. + var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. + for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { + var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", + "registrationPoint", "rotation", "parentID"]); + var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, + curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation); + var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position); + var distance = Vec3.dot(delta, targetDirection); + deltaParallel = Math.min(distance, deltaParallel); + deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), + deltaPerpendicular); + entityPositions[i] = curLoopEntityProps.position; + entityParentIDs[i] = curLoopEntityProps.parentID; + } + deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); + deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); + } + + if (grid.getSnapToGrid()) { + var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", + "registrationPoint"]); + var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position); + position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions, + firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint); + deltaPosition = Vec3.subtract(position, firstEntityProps.position); + } + + if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { + for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { + if (Uuid.isNull(entityParentIDs[editEntityIndex])) { + Entities.editEntity(pastedEntityIDs[editEntityIndex], { + position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex]) + }); + } + } + } + } + + if (isWearable) { + // apply the relative offsets saved during checkout + var offsets = {}; + if (wearableLocalPosition) { + offsets.localPosition = wearableLocalPosition; + } + if (wearableLocalRotation) { + offsets.localRotation = wearableLocalRotation; + } + if (wearableLocalDimensions) { + offsets.localDimensions = wearableLocalDimensions; + } else if (wearableDimensions) { + offsets.dimensions = wearableDimensions; + } + // we currently assume a wearable is a single entity + Entities.editEntity(pastedEntityIDs[0], offsets); + } + + var rezPosition = Entities.getEntityProperties(pastedEntityIDs[0], "position").position; + + Audio.playSound(REZZING_SOUND, { + volume: 1.0, + position: rezPosition, + localOnly: true }); + + } else { + Window.notifyEditError("Can't import entities: entities would be out of bounds."); } + } else { + Window.notifyEditError("There was an error importing the entity file."); } +} - function deleteSendAssetParticleEffect() { - if (sendAssetParticleEffectUpdateTimer) { - Script.clearInterval(sendAssetParticleEffectUpdateTimer); - sendAssetParticleEffectUpdateTimer = null; - } - if (sendAssetParticleEffect) { - sendAssetParticleEffect = Entities.deleteEntity(sendAssetParticleEffect); - } - sendAssetRecipient = null; - } - - var savedDisablePreviewOptionLocked = false; - var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview");; - function maybeEnableHMDPreview() { - // Set a small timeout to prevent sensitive data from being shown during - // UI fade - Script.setTimeout(function () { - setTabletVisibleInSecondaryCamera(true); - DesktopPreviewProvider.setPreviewDisabledReason("USER"); - Menu.setIsOptionChecked("Disable Preview", savedDisablePreviewOption); - savedDisablePreviewOptionLocked = false; - }, 150); - } - - // Function Name: fromQml() - // - // Description: - // -Called when a message is received from Checkout.qml. The "message" argument is what is sent from the Checkout QML - // in the format "{method, params}", like json-rpc. - function fromQml(message) { - switch (message.method) { - case 'purchases_openWallet': - case 'checkout_openWallet': - case 'checkout_setUpClicked': - openWallet(); - break; - case 'purchases_walletNotSetUp': - wireEventBridge(true); - sendToQml({ - method: 'updateWalletReferrer', - referrer: "purchases" - }); - openWallet(); - break; - case 'checkout_walletNotSetUp': - wireEventBridge(true); - sendToQml({ - method: 'updateWalletReferrer', - referrer: message.referrer === "itemPage" ? message.itemId : message.referrer - }); - openWallet(); - break; - case 'checkout_cancelClicked': - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL); - // TODO: Make Marketplace a QML app that's a WebView wrapper so we can use the app stack. - // I don't think this is trivial to do since we also want to inject some JS into the DOM. - //tablet.popFromStack(); - break; - case 'header_goToPurchases': - case 'checkout_goToPurchases': - referrerURL = MARKETPLACE_URL_INITIAL; - filterText = message.filterText; - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); - break; - case 'checkout_itemLinkClicked': - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'checkout_continueShopping': - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - //tablet.popFromStack(); - break; - case 'purchases_itemInfoClicked': - var itemId = message.itemId; - if (itemId && itemId !== "") { - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL); - } - break; - case 'checkout_rezClicked': - case 'purchases_rezClicked': - rezEntity(message.itemHref, message.itemType); - break; - case 'header_marketplaceImageClicked': - case 'purchases_backClicked': - tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'purchases_goToMarketplaceClicked': - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'updateItemClicked': - tablet.gotoWebScreen(message.upgradeUrl + "?edition=" + message.itemEdition, - MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'giftAsset': - - break; - case 'passphrasePopup_cancelClicked': - case 'needsLogIn_cancelClicked': - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'needsLogIn_loginClicked': - openLoginWindow(); - break; - case 'disableHmdPreview': - if (!savedDisablePreviewOption) { - savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview"); - savedDisablePreviewOptionLocked = true; - } - - if (!savedDisablePreviewOption) { - DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN"); - Menu.setIsOptionChecked("Disable Preview", true); - setTabletVisibleInSecondaryCamera(false); - } - break; - case 'maybeEnableHmdPreview': - maybeEnableHMDPreview(); - break; - case 'purchases_openGoTo': - tablet.loadQMLSource("hifi/tablet/TabletAddressDialog.qml"); - break; - case 'purchases_itemCertificateClicked': - setCertificateInfo("", message.itemCertificateId); - break; - case 'inspectionCertificate_closeClicked': - tablet.gotoHomeScreen(); - break; - case 'inspectionCertificate_requestOwnershipVerification': - ContextOverlay.requestOwnershipVerification(message.entity); - break; - case 'inspectionCertificate_showInMarketplaceClicked': - tablet.gotoWebScreen(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'header_myItemsClicked': - referrerURL = MARKETPLACE_URL_INITIAL; - filterText = ""; - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); - wireEventBridge(true); - sendToQml({ - method: 'purchases_showMyItems' - }); - break; - case 'refreshConnections': - // Guard to prevent this code from being executed while sending money -- - // we only want to execute this while sending non-HFC gifts - if (!onWalletScreen) { - print('Refreshing Connections...'); - getConnectionData(false); - } - break; - case 'enable_ChooseRecipientNearbyMode': - // Guard to prevent this code from being executed while sending money -- - // we only want to execute this while sending non-HFC gifts - if (!onWalletScreen) { - if (!isUpdateOverlaysWired) { - Script.update.connect(updateOverlays); - isUpdateOverlaysWired = true; - } - } - break; - case 'disable_ChooseRecipientNearbyMode': - // Guard to prevent this code from being executed while sending money -- - // we only want to execute this while sending non-HFC gifts - if (!onWalletScreen) { - if (isUpdateOverlaysWired) { - Script.update.disconnect(updateOverlays); - isUpdateOverlaysWired = false; - } - removeOverlays(); - } - break; - case 'wallet_availableUpdatesReceived': - case 'purchases_availableUpdatesReceived': - userHasUpdates = message.numUpdates > 0; - messagesWaiting(userHasUpdates); - break; - case 'purchases_updateWearables': - var currentlyWornWearables = []; - var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case) - - var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS); - - for (var i = 0; i < nearbyEntities.length; i++) { - var currentProperties = Entities.getEntityProperties(nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID']); - if (currentProperties.parentID === MyAvatar.sessionUUID) { - currentlyWornWearables.push({ - entityID: nearbyEntities[i], - entityCertID: currentProperties.certificateID, - entityEdition: currentProperties.editionNumber - }); - } - } - - sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables }); - break; - case 'sendAsset_sendPublicly': - if (message.assetName !== "") { - deleteSendAssetParticleEffect(); - sendAssetRecipient = message.recipient; - var amount = message.amount; - var props = SEND_ASSET_PARTICLE_PROPERTIES; - props.parentID = MyAvatar.sessionUUID; - props.position = MyAvatar.position; - props.position.y += 0.2; - if (message.effectImage) { - props.textures = message.effectImage; - } - sendAssetParticleEffect = Entities.addEntity(props, true); - particleEffectTimestamp = Date.now(); - updateSendAssetParticleEffect(); - sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect, SEND_ASSET_PARTICLE_TIMER_UPDATE); - } - break; - case 'http.request': - // Handled elsewhere, don't log. - break; - case 'goToPurchases_fromWalletHome': // HRS FIXME What's this about? - break; - default: - print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); - } - } - - // Function Name: wireEventBridge() - // - // Description: - // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or - // disable to event bridge. - // - // Relevant Variables: - // -hasEventBridge: true/false depending on whether we've already connected the event bridge. - var hasEventBridge = false; - function wireEventBridge(on) { - if (!tablet) { - print("Warning in wireEventBridge(): 'tablet' undefined!"); +var referrerURL; // Used for updating Purchases QML +var filterText; // Used for updating Purchases QML +function onWebEventReceived(message) { + message = JSON.parse(message); + if (message.type === GOTO_DIRECTORY) { + ui.open(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); + } else if (message.type === QUERY_CAN_WRITE_ASSETS) { + ui.sendToHtml(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); + } else if (message.type === WARN_USER_NO_PERMISSIONS) { + Window.alert(NO_PERMISSIONS_ERROR_MESSAGE); + } else if (message.type === CLARA_IO_STATUS) { + if (isDownloadBeingCancelled) { return; } - if (on) { - if (!hasEventBridge) { - tablet.fromQml.connect(fromQml); - hasEventBridge = true; - } + + var text = message.status; + if (messageBox === null) { + messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); } else { - if (hasEventBridge) { - tablet.fromQml.disconnect(fromQml); - hasEventBridge = false; - } + Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); } - } - - // Function Name: onTabletScreenChanged() - // - // Description: - // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string - // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. - var onWalletScreen = false; - var onCommerceScreen = false; - function onTabletScreenChanged(type, url) { - onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; - var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; - var onCommerceScreenNow = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH - || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); - - if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { // exiting wallet or commerce screen - maybeEnableHMDPreview(); + return; + } else if (message.type === CLARA_IO_DOWNLOAD) { + if (messageBox !== null) { + Window.closeMessageBox(messageBox); + messageBox = null; } - - onCommerceScreen = onCommerceScreenNow; - onWalletScreen = onWalletScreenNow; - wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); - - if (url === MARKETPLACE_PURCHASES_QML_PATH) { - sendToQml({ - method: 'updatePurchases', - referrerURL: referrerURL, - filterText: filterText - }); - } - - // for toolbar mode: change button to active when window is first openend, false otherwise. - if (marketplaceButton) { - marketplaceButton.editProperties({ isActive: (onMarketplaceScreen || onCommerceScreen) && !onWalletScreen }); - } - if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { - ContextOverlay.isInMarketplaceInspectionMode = true; - } else { - ContextOverlay.isInMarketplaceInspectionMode = false; - } - - if (onCommerceScreen) { - if (!isWired) { - Users.usernameFromIDReply.connect(usernameFromIDReply); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - triggerMapping.enable(); - triggerPressMapping.enable(); - } - isWired = true; - Wallet.refreshWalletStatus(); - } else { - off(); - sendToQml({ - method: 'inspectionCertificate_resetCert' - }); - } - } - - // - // Manage the connection between the button and the window. - // - var marketplaceButton; - var buttonName = "MARKET"; - var tablet = null; - var NORMAL_ICON = "icons/tablet-icons/market-i.svg"; - var NORMAL_ACTIVE = "icons/tablet-icons/market-a.svg"; - var WAITING_ICON = "icons/tablet-icons/market-i-msg.svg"; - var WAITING_ACTIVE = "icons/tablet-icons/market-a-msg.svg"; - function startup() { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - marketplaceButton = tablet.addButton({ - icon: NORMAL_ICON, - activeIcon: NORMAL_ACTIVE, - text: buttonName, - sortOrder: 9 + return; + } else if (message.type === CLARA_IO_CANCELLED_DOWNLOAD) { + isDownloadBeingCancelled = false; + } else if (message.type === "CHECKOUT") { + wireQmlEventBridge(true); + ui.open(MARKETPLACE_CHECKOUT_QML_PATH); + ui.tablet.sendToQml({ + method: 'updateCheckoutQML', + params: message + }); + } else if (message.type === "REQUEST_SETTING") { + sendCommerceSettings(); + } else if (message.type === "PURCHASES") { + referrerURL = message.referrerURL; + filterText = ""; + ui.open(MARKETPLACE_PURCHASES_QML_PATH); + } else if (message.type === "LOGIN") { + openLoginWindow(); + } else if (message.type === "WALLET_SETUP") { + wireQmlEventBridge(true); + ui.tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: "marketplace cta" + }); + openWallet(); + } else if (message.type === "MY_ITEMS") { + referrerURL = MARKETPLACE_URL_INITIAL; + filterText = ""; + ui.open(MARKETPLACE_PURCHASES_QML_PATH); + wireQmlEventBridge(true); + ui.tablet.sendToQml({ + method: 'purchases_showMyItems' }); - - ContextOverlay.contextOverlayClicked.connect(setCertificateInfo); - Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); - GlobalServices.myUsernameChanged.connect(onUsernameChanged); - marketplaceButton.clicked.connect(onButtonClicked); - tablet.screenChanged.connect(onTabletScreenChanged); - tablet.webEventReceived.connect(onMessage); - Wallet.walletStatusChanged.connect(sendCommerceSettings); - Window.messageBoxClosed.connect(onMessageBoxClosed); - - Wallet.refreshWalletStatus(); } - var isWired = false; - var isUpdateOverlaysWired = false; - function off() { - if (isWired) { - Users.usernameFromIDReply.disconnect(usernameFromIDReply); - Controller.mousePressEvent.disconnect(handleMouseEvent); - Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); - triggerMapping.disable(); - triggerPressMapping.disable(); +} +var sendAssetRecipient; +var sendAssetParticleEffectUpdateTimer; +var particleEffectTimestamp; +var sendAssetParticleEffect; +var SEND_ASSET_PARTICLE_TIMER_UPDATE = 250; +var SEND_ASSET_PARTICLE_EMITTING_DURATION = 3000; +var SEND_ASSET_PARTICLE_LIFETIME_SECONDS = 8; +var SEND_ASSET_PARTICLE_PROPERTIES = { + accelerationSpread: { x: 0, y: 0, z: 0 }, + alpha: 1, + alphaFinish: 1, + alphaSpread: 0, + alphaStart: 1, + azimuthFinish: 0, + azimuthStart: -6, + color: { red: 255, green: 222, blue: 255 }, + colorFinish: { red: 255, green: 229, blue: 225 }, + colorSpread: { red: 0, green: 0, blue: 0 }, + colorStart: { red: 243, green: 255, blue: 255 }, + emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0 }, + emitRate: 4, + emitSpeed: 2.1, + emitterShouldTrail: true, + isEmitting: 1, + lifespan: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate + lifetime: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, + maxParticles: 20, + name: 'asset-particles', + particleRadius: 0.2, + polarFinish: 0, + polarStart: 0, + radiusFinish: 0.05, + radiusSpread: 0, + radiusStart: 0.2, + speedSpread: 0, + textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png", + type: 'ParticleEffect' +}; - isWired = false; - } - if (isUpdateOverlaysWired) { - Script.update.disconnect(updateOverlays); - isUpdateOverlaysWired = false; - } - removeOverlays(); - } - function shutdown() { - maybeEnableHMDPreview(); +function updateSendAssetParticleEffect() { + var timestampNow = Date.now(); + if ((timestampNow - particleEffectTimestamp) > (SEND_ASSET_PARTICLE_LIFETIME_SECONDS * 1000)) { deleteSendAssetParticleEffect(); + return; + } else if ((timestampNow - particleEffectTimestamp) > SEND_ASSET_PARTICLE_EMITTING_DURATION) { + Entities.editEntity(sendAssetParticleEffect, { + isEmitting: 0 + }); + } else if (sendAssetParticleEffect) { + var recipientPosition = AvatarList.getAvatar(sendAssetRecipient).position; + var distance = Vec3.distance(recipientPosition, MyAvatar.position); + var accel = Vec3.subtract(recipientPosition, MyAvatar.position); + accel.y -= 3.0; + var life = Math.sqrt(2 * distance / Vec3.length(accel)); + Entities.editEntity(sendAssetParticleEffect, { + emitAcceleration: accel, + lifespan: life + }); + } +} - ContextOverlay.contextOverlayClicked.disconnect(setCertificateInfo); - Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); - GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); - marketplaceButton.clicked.disconnect(onButtonClicked); - tablet.removeButton(marketplaceButton); - tablet.webEventReceived.disconnect(onMessage); - Wallet.walletStatusChanged.disconnect(sendCommerceSettings); - Window.messageBoxClosed.disconnect(onMessageBoxClosed); +function deleteSendAssetParticleEffect() { + if (sendAssetParticleEffectUpdateTimer) { + Script.clearInterval(sendAssetParticleEffectUpdateTimer); + sendAssetParticleEffectUpdateTimer = null; + } + if (sendAssetParticleEffect) { + sendAssetParticleEffect = Entities.deleteEntity(sendAssetParticleEffect); + } + sendAssetRecipient = null; +} + +var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview"); +var UI_FADE_TIMEOUT_MS = 150; +function maybeEnableHMDPreview() { + // Set a small timeout to prevent sensitive data from being shown during UI fade + Script.setTimeout(function () { + setTabletVisibleInSecondaryCamera(true); + DesktopPreviewProvider.setPreviewDisabledReason("USER"); + Menu.setIsOptionChecked("Disable Preview", savedDisablePreviewOption); + }, UI_FADE_TIMEOUT_MS); +} - if (tablet) { - tablet.screenChanged.disconnect(onTabletScreenChanged); - if (onMarketplaceScreen) { - tablet.gotoHomeScreen(); +var onQmlMessageReceived = function onQmlMessageReceived(message) { + if (message.messageSrc === "HTML") { + return; + } + switch (message.method) { + case 'purchases_openWallet': + case 'checkout_openWallet': + case 'checkout_setUpClicked': + openWallet(); + break; + case 'purchases_walletNotSetUp': + wireQmlEventBridge(true); + ui.tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: "purchases" + }); + openWallet(); + break; + case 'checkout_walletNotSetUp': + wireQmlEventBridge(true); + ui.tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: message.referrer === "itemPage" ? message.itemId : message.referrer + }); + openWallet(); + break; + case 'checkout_cancelClicked': + ui.open(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'header_goToPurchases': + case 'checkout_goToPurchases': + referrerURL = MARKETPLACE_URL_INITIAL; + filterText = message.filterText; + ui.open(MARKETPLACE_PURCHASES_QML_PATH); + break; + case 'checkout_itemLinkClicked': + ui.open(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'checkout_continueShopping': + ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'purchases_itemInfoClicked': + var itemId = message.itemId; + if (itemId && itemId !== "") { + ui.open(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL); + } + break; + case 'checkout_rezClicked': + case 'purchases_rezClicked': + rezEntity(message.itemHref, message.itemType); + break; + case 'header_marketplaceImageClicked': + case 'purchases_backClicked': + ui.open(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'purchases_goToMarketplaceClicked': + ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'updateItemClicked': + ui.open(message.upgradeUrl + "?edition=" + message.itemEdition, + MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'giftAsset': + + break; + case 'passphrasePopup_cancelClicked': + case 'needsLogIn_cancelClicked': + ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'needsLogIn_loginClicked': + openLoginWindow(); + break; + case 'disableHmdPreview': + if (!savedDisablePreviewOption) { + savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview"); + } + + if (!savedDisablePreviewOption) { + DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN"); + Menu.setIsOptionChecked("Disable Preview", true); + setTabletVisibleInSecondaryCamera(false); + } + break; + case 'maybeEnableHmdPreview': + maybeEnableHMDPreview(); + break; + case 'purchases_openGoTo': + ui.open("hifi/tablet/TabletAddressDialog.qml"); + break; + case 'purchases_itemCertificateClicked': + contextOverlayEntity = ""; + setCertificateInfo(contextOverlayEntity, message.itemCertificateId); + break; + case 'inspectionCertificate_closeClicked': + ui.close(); + break; + case 'inspectionCertificate_requestOwnershipVerification': + ContextOverlay.requestOwnershipVerification(message.entity); + break; + case 'inspectionCertificate_showInMarketplaceClicked': + ui.open(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'header_myItemsClicked': + referrerURL = MARKETPLACE_URL_INITIAL; + filterText = ""; + ui.open(MARKETPLACE_PURCHASES_QML_PATH); + wireQmlEventBridge(true); + ui.tablet.sendToQml({ + method: 'purchases_showMyItems' + }); + break; + case 'refreshConnections': + // Guard to prevent this code from being executed while sending money -- + // we only want to execute this while sending non-HFC gifts + if (!onWalletScreen) { + print('Refreshing Connections...'); + getConnectionData(false); + } + break; + case 'enable_ChooseRecipientNearbyMode': + // Guard to prevent this code from being executed while sending money -- + // we only want to execute this while sending non-HFC gifts + if (!onWalletScreen) { + if (!isUpdateOverlaysWired) { + Script.update.connect(updateOverlays); + isUpdateOverlaysWired = true; + } + } + break; + case 'disable_ChooseRecipientNearbyMode': + // Guard to prevent this code from being executed while sending money -- + // we only want to execute this while sending non-HFC gifts + if (!onWalletScreen) { + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); + } + break; + case 'wallet_availableUpdatesReceived': + case 'purchases_availableUpdatesReceived': + userHasUpdates = message.numUpdates > 0; + ui.messagesWaiting(userHasUpdates); + break; + case 'purchases_updateWearables': + var currentlyWornWearables = []; + var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case) + + var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS); + + for (var i = 0; i < nearbyEntities.length; i++) { + var currentProperties = Entities.getEntityProperties( + nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID'] + ); + if (currentProperties.parentID === MyAvatar.sessionUUID) { + currentlyWornWearables.push({ + entityID: nearbyEntities[i], + entityCertID: currentProperties.certificateID, + entityEdition: currentProperties.editionNumber + }); } } + ui.tablet.sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables }); + break; + case 'sendAsset_sendPublicly': + if (message.assetName !== "") { + deleteSendAssetParticleEffect(); + sendAssetRecipient = message.recipient; + var props = SEND_ASSET_PARTICLE_PROPERTIES; + props.parentID = MyAvatar.sessionUUID; + props.position = MyAvatar.position; + props.position.y += 0.2; + if (message.effectImage) { + props.textures = message.effectImage; + } + sendAssetParticleEffect = Entities.addEntity(props, true); + particleEffectTimestamp = Date.now(); + updateSendAssetParticleEffect(); + sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect, + SEND_ASSET_PARTICLE_TIMER_UPDATE); + } + break; + case 'http.request': + // Handled elsewhere, don't log. + break; + case 'goToPurchases_fromWalletHome': // HRS FIXME What's this about? + break; + default: + print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); + } +}; + +// Function Name: onTabletScreenChanged() +// +// Description: +// -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string +// value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. +var onMarketplaceScreen = false; +var onWalletScreen = false; +var onCommerceScreen = false; +var onInspectionCertificateScreen = false; +var onTabletScreenChanged = function onTabletScreenChanged(type, url) { + ui.setCurrentVisibleScreenMetadata(type, url); + onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; + onInspectionCertificateScreen = type === "QML" && url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1; + var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; + var onCommerceScreenNow = type === "QML" && + (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH || + onInspectionCertificateScreen); + + // exiting wallet or commerce screen + if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { + maybeEnableHMDPreview(); + } + + onCommerceScreen = onCommerceScreenNow; + onWalletScreen = onWalletScreenNow; + wireQmlEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); + + if (url === MARKETPLACE_PURCHASES_QML_PATH) { + ui.tablet.sendToQml({ + method: 'updatePurchases', + referrerURL: referrerURL, + filterText: filterText + }); + } + + ui.isOpen = (onMarketplaceScreen || onCommerceScreen) && !onWalletScreen; + ui.buttonActive(ui.isOpen); + + if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { + ContextOverlay.isInMarketplaceInspectionMode = true; + } else { + ContextOverlay.isInMarketplaceInspectionMode = false; + } + + if (onInspectionCertificateScreen) { + setCertificateInfo(contextOverlayEntity); + } + + if (onCommerceScreen) { + if (!isWired) { + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + } + isWired = true; + Wallet.refreshWalletStatus(); + } else { + ui.tablet.sendToQml({ + method: 'inspectionCertificate_resetCert' + }); off(); } + console.debug(ui.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type + + "\nNew screen URL: " + url + "\nCurrent app open status: " + ui.isOpen + "\n"); +}; - // - // Run the functions. - // - startup(); - Script.scriptEnding.connect(shutdown); +// +// Manage the connection between the button and the window. +// +var BUTTON_NAME = "MARKET"; +var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; +var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. +var ui; +function startup() { + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 9, + inject: MARKETPLACES_INJECT_SCRIPT_URL, + home: MARKETPLACE_URL_INITIAL, + onScreenChanged: onTabletScreenChanged, + onMessage: onQmlMessageReceived + }); + ContextOverlay.contextOverlayClicked.connect(openInspectionCertificateQML); + Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); + GlobalServices.myUsernameChanged.connect(onUsernameChanged); + ui.tablet.webEventReceived.connect(onWebEventReceived); + Wallet.walletStatusChanged.connect(sendCommerceSettings); + Window.messageBoxClosed.connect(onMessageBoxClosed); + Wallet.refreshWalletStatus(); +} + +var isWired = false; +var isUpdateOverlaysWired = false; +function off() { + if (isWired) { + Users.usernameFromIDReply.disconnect(usernameFromIDReply); + Controller.mousePressEvent.disconnect(handleMouseEvent); + Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); + triggerMapping.disable(); + triggerPressMapping.disable(); + + isWired = false; + } + + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); +} +function shutdown() { + maybeEnableHMDPreview(); + deleteSendAssetParticleEffect(); + + Window.messageBoxClosed.disconnect(onMessageBoxClosed); + Wallet.walletStatusChanged.disconnect(sendCommerceSettings); + ui.tablet.webEventReceived.disconnect(onWebEventReceived); + GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); + Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); + ContextOverlay.contextOverlayClicked.disconnect(openInspectionCertificateQML); + + off(); +} + +// +// Run the functions. +// +startup(); +Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/scripts/system/modules/createWindow.js b/scripts/system/modules/createWindow.js index 7369cf91f8..0c4412abfb 100644 --- a/scripts/system/modules/createWindow.js +++ b/scripts/system/modules/createWindow.js @@ -125,6 +125,9 @@ module.exports = (function() { Script.scriptEnding.connect(this, function() { this.window.close(); + // FIXME: temp solution for reload crash (MS18269), + // we should decide on proper object ownership strategy for InteractiveWindow API + this.window = null; }); }, setVisible: function(visible) { diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 0778e2a44b..36fe264274 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -564,7 +564,7 @@ } function walletNotSetup() { - createNotification("Your wallet isn't set up. Open the WALLET app.", NotificationType.WALLET); + createNotification("Your wallet isn't activated yet. Open the WALLET app.", NotificationType.WALLET); } function connectionAdded(connectionName) { diff --git a/scripts/system/pal.js b/scripts/system/pal.js index ebb45130e5..85898c28fb 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -1,6 +1,9 @@ "use strict"; -/*jslint vars:true, plusplus:true, forin:true*/ -/*global Tablet, Settings, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation*/ +/* jslint vars:true, plusplus:true, forin:true */ +/* global Tablet, Settings, Script, AvatarList, Users, Entities, + MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, + UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation +*/ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // pal.js @@ -20,7 +23,7 @@ var AppUi = Script.require('appUi'); var populateNearbyUserList, color, textures, removeOverlays, controllerComputePickRay, off, receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL, - CHANNEL, getConnectionData, findableByChanged, + CHANNEL, getConnectionData, avatarAdded, avatarRemoved, avatarSessionChanged; // forward references; // hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed @@ -318,6 +321,10 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See break; case 'http.request': break; // Handled by request-service. + case 'hideNotificationDot': + shouldShowDot = false; + ui.messagesWaiting(shouldShowDot); + break; default: print('Unrecognized message from Pal.qml:', JSON.stringify(message)); } @@ -361,8 +368,8 @@ function getProfilePicture(username, callback) { // callback(url) if successfull }); } var SAFETY_LIMIT = 400; -function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise) - var url = METAVERSE_BASE + '/api/v1/users?per_page=' + SAFETY_LIMIT + '&'; +function getAvailableConnections(domain, callback, numResultsPerPage) { // callback([{usename, location}...]) if successfull. (Logs otherwise) + var url = METAVERSE_BASE + '/api/v1/users?per_page=' + (numResultsPerPage || SAFETY_LIMIT) + '&'; if (domain) { url += 'status=' + domain.slice(1, -1); // without curly braces } else { @@ -713,7 +720,7 @@ function tabletVisibilityChanged() { if (!ui.tablet.tabletShown && ui.isOpen) { ui.close(); } - } +} var UPDATE_INTERVAL_MS = 100; var updateInterval; @@ -725,10 +732,14 @@ function createUpdateInterval() { var previousContextOverlay = ContextOverlay.enabled; var previousRequestsDomainListData = Users.requestsDomainListData; -function on() { +function palOpened() { + ui.sendMessage({ + method: 'changeConnectionsDotStatus', + shouldShowDot: shouldShowDot + }); previousContextOverlay = ContextOverlay.enabled; - previousRequestsDomainListData = Users.requestsDomainListData + previousRequestsDomainListData = Users.requestsDomainListData; ContextOverlay.enabled = false; Users.requestsDomainListData = true; @@ -807,14 +818,98 @@ function avatarSessionChanged(avatarID) { sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarSessionChanged'] }); } +function notificationDataProcessPage(data) { + return data.data.users; +} + +var shouldShowDot = false; +var storedOnlineUsersArray = []; +function notificationPollCallback(connectionsArray) { + // + // START logic for handling online/offline user changes + // + var i, j; + var newlyOnlineConnectionsArray = []; + for (i = 0; i < connectionsArray.length; i++) { + var currentUser = connectionsArray[i]; + + if (connectionsArray[i].online) { + var indexOfStoredOnlineUser = -1; + for (j = 0; j < storedOnlineUsersArray.length; j++) { + if (currentUser.username === storedOnlineUsersArray[j].username) { + indexOfStoredOnlineUser = j; + break; + } + } + // If the user record isn't already presesnt inside `storedOnlineUsersArray`... + if (indexOfStoredOnlineUser < 0) { + storedOnlineUsersArray.push(currentUser); + newlyOnlineConnectionsArray.push(currentUser); + } + } else { + var indexOfOfflineUser = -1; + for (j = 0; j < storedOnlineUsersArray.length; j++) { + if (currentUser.username === storedOnlineUsersArray[j].username) { + indexOfOfflineUser = j; + break; + } + } + if (indexOfOfflineUser >= 0) { + storedOnlineUsersArray.splice(indexOfOfflineUser); + } + } + } + // If there's new data, the light should turn on. + // If the light is already on and you have connections online, the light should stay on. + // In all other cases, the light should turn off or stay off. + shouldShowDot = newlyOnlineConnectionsArray.length > 0 || (storedOnlineUsersArray.length > 0 && shouldShowDot); + // + // END logic for handling online/offline user changes + // + + if (!ui.isOpen) { + ui.messagesWaiting(shouldShowDot); + ui.sendMessage({ + method: 'changeConnectionsDotStatus', + shouldShowDot: shouldShowDot + }); + + if (newlyOnlineConnectionsArray.length > 0) { + var message; + if (!ui.notificationInitialCallbackMade) { + message = newlyOnlineConnectionsArray.length + " of your connections " + + (newlyOnlineConnectionsArray.length === 1 ? "is" : "are") + " online. Open PEOPLE to join them!"; + ui.notificationDisplayBanner(message); + } else { + for (i = 0; i < newlyOnlineConnectionsArray.length; i++) { + message = newlyOnlineConnectionsArray[i].username + " is available in " + + newlyOnlineConnectionsArray[i].location.root.name + ". Open PEOPLE to join them!"; + ui.notificationDisplayBanner(message); + } + } + } + } +} + +function isReturnedDataEmpty(data) { + var usersArray = data.data.users; + return usersArray.length === 0; +} + function startup() { ui = new AppUi({ buttonName: "PEOPLE", sortOrder: 7, home: "hifi/Pal.qml", - onOpened: on, + onOpened: palOpened, onClosed: off, - onMessage: fromQml + onMessage: fromQml, + notificationPollEndpoint: "/api/v1/users?filter=connections&per_page=10", + notificationPollTimeoutMs: 60000, + notificationDataProcessPage: notificationDataProcessPage, + notificationPollCallback: notificationPollCallback, + notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, + notificationPollCaresAboutSince: false }); Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 37270f896e..3d744b3bd2 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -7,28 +7,19 @@ // Distributed under the Apache License, Version 2.0 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar, Snapshot */ +/* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, + OverlayWebWindow, Desktop, Account, MyAvatar, Snapshot */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ (function () { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/accountUtils.js"); +var AppUi = Script.require('appUi'); var SNAPSHOT_DELAY = 500; // 500ms var FINISH_SOUND_DELAY = 350; var resetOverlays; var reticleVisible; -var buttonName = "SNAP"; -var buttonConnected = false; - -var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); -var button = tablet.addButton({ - icon: "icons/tablet-icons/snap-i.svg", - activeIcon: "icons/tablet-icons/snap-a.svg", - text: buttonName, - sortOrder: 5 -}); - var snapshotOptions = {}; var imageData = []; var storyIDsToMaybeDelete = []; @@ -52,8 +43,6 @@ try { print('Failed to resolve request api, error: ' + err); } - - function removeFromStoryIDsToMaybeDelete(story_id) { storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1); print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); @@ -73,33 +62,32 @@ function onMessage(message) { // 2. Although we currently use a single image, we would like to take snapshot, a selfie, a 360 etc. all at the // same time, show the user all of them, and have the user deselect any that they do not want to share. // So we'll ultimately be receiving a set of objects, perhaps with different post processing for each. - message = JSON.parse(message); if (message.type !== "snapshot") { return; } switch (message.action) { case 'ready': // DOM is ready and page has loaded - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "captureSettings", setting: Settings.getValue("alsoTakeAnimatedSnapshot", true) - })); + }); if (Snapshot.getSnapshotsLocation() !== "") { isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) { - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "showPreviousImages", options: snapshotOptions, image_data: imageData, canShare: canShare - })); + }); }); } else { - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "showSetupInstructions" - })); + }); Settings.setValue("previousStillSnapPath", ""); Settings.setValue("previousStillSnapStoryID", ""); Settings.setValue("previousStillSnapBlastingDisabled", false); @@ -124,7 +112,7 @@ function onMessage(message) { || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog"); } else { - tablet.loadQMLOnTop("hifi/tablet/TabletGeneralPreferences.qml"); + ui.openNewAppOnTop("hifi/tablet/TabletGeneralPreferences.qml"); } break; case 'captureStillAndGif': @@ -284,7 +272,6 @@ var POLAROID_RATE_LIMIT_MS = 1000; var polaroidPrintingIsRateLimited = false; function printToPolaroid(image_url) { - // Rate-limit printing if (polaroidPrintingIsRateLimited) { return; @@ -376,19 +363,6 @@ function fillImageDataFromPrevious() { } } -var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html"); -var isInSnapshotReview = false; -function onButtonClicked() { - if (isInSnapshotReview){ - // for toolbar-mode: go back to home screen, this will close the window. - tablet.gotoHomeScreen(); - } else { - fillImageDataFromPrevious(); - tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); - HMD.openTablet(); - } -} - function snapshotUploaded(isError, reply) { if (!isError) { var replyJson = JSON.parse(reply), @@ -409,12 +383,12 @@ function snapshotUploaded(isError, reply) { } if ((isGif && !ignoreGifSnapshotData) || (!isGif && !ignoreStillSnapshotData)) { print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID); - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "snapshotUploadComplete", story_id: storyID, image_url: imageURL, - })); + }); if (isGif) { Settings.setValue("previousAnimatedSnapStoryID", storyID); } else { @@ -429,10 +403,10 @@ function snapshotUploaded(isError, reply) { } var href, snapshotDomainID; function takeSnapshot() { - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "clearPreviousImages" - })); + }); Settings.setValue("previousStillSnapPath", ""); Settings.setValue("previousStillSnapStoryID", ""); Settings.setValue("previousStillSnapBlastingDisabled", false); @@ -471,10 +445,6 @@ function takeSnapshot() { } else { Window.stillSnapshotTaken.connect(stillSnapshotTaken); } - if (buttonConnected) { - button.clicked.disconnect(onButtonClicked); - buttonConnected = false; - } // hide overlays if they are on if (resetOverlays) { @@ -538,10 +508,6 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { Menu.setIsOptionChecked("Show Overlays", true); } Window.stillSnapshotTaken.disconnect(stillSnapshotTaken); - if (!buttonConnected) { - button.clicked.connect(onButtonClicked); - buttonConnected = true; - } // A Snapshot Review dialog might be left open indefinitely after taking the picture, // during which time the user may have moved. So stash that info in the dialog so that @@ -559,12 +525,12 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { isLoggedIn: isLoggedIn }; imageData = [{ localPath: pathStillSnapshot, href: href }]; - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "addImages", options: snapshotOptions, image_data: imageData - })); + }); }); } @@ -572,10 +538,10 @@ function snapshotDirChanged(snapshotPath) { Window.browseDirChanged.disconnect(snapshotDirChanged); if (snapshotPath !== "") { // not cancelled Snapshot.setSnapshotsLocation(snapshotPath); - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "snapshotLocationChosen" - })); + }); } } @@ -603,22 +569,18 @@ function processingGifStarted(pathStillSnapshot) { isLoggedIn: isLoggedIn }; imageData = [{ localPath: pathStillSnapshot, href: href }]; - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "addImages", options: snapshotOptions, image_data: imageData - })); + }); }); } function processingGifCompleted(pathAnimatedSnapshot) { isLoggedIn = Account.isLoggedIn(); Window.processingGifCompleted.disconnect(processingGifCompleted); - if (!buttonConnected) { - button.clicked.connect(onButtonClicked); - buttonConnected = true; - } Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot); @@ -631,12 +593,12 @@ function processingGifCompleted(pathAnimatedSnapshot) { canBlast: location.domainID === Settings.getValue("previousSnapshotDomainID"), }; imageData = [{ localPath: pathAnimatedSnapshot, href: href }]; - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "addImages", options: snapshotOptions, image_data: imageData - })); + }); }); } function maybeDeleteSnapshotStories() { @@ -655,28 +617,16 @@ function maybeDeleteSnapshotStories() { }); storyIDsToMaybeDelete = []; } -function onTabletScreenChanged(type, url) { - var wasInSnapshotReview = isInSnapshotReview; - isInSnapshotReview = (type === "Web" && url === SNAPSHOT_REVIEW_URL); - button.editProperties({ isActive: isInSnapshotReview }); - if (isInSnapshotReview !== wasInSnapshotReview) { - if (isInSnapshotReview) { - tablet.webEventReceived.connect(onMessage); - } else { - tablet.webEventReceived.disconnect(onMessage); - } - } -} function onUsernameChanged() { fillImageDataFromPrevious(); isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) { - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "showPreviousImages", options: snapshotOptions, image_data: imageData, canShare: canShare - })); + }); }); if (isLoggedIn) { if (shareAfterLogin) { @@ -705,10 +655,10 @@ function onUsernameChanged() { function snapshotLocationSet(location) { if (location !== "") { - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "snapshotLocationChosen" - })); + }); } } @@ -733,36 +683,36 @@ function processRezPermissionChange(canRez) { action = 'setPrintButtonDisabled'; } - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action : action - })); + }); } -button.clicked.connect(onButtonClicked); -buttonConnected = true; +function startup() { + ui = new AppUi({ + buttonName: "SNAP", + sortOrder: 5, + home: Script.resolvePath("html/SnapshotReview.html"), + onOpened: fillImageDataFromPrevious, + onMessage: onMessage + }); -Window.snapshotShared.connect(snapshotUploaded); -tablet.screenChanged.connect(onTabletScreenChanged); -GlobalServices.myUsernameChanged.connect(onUsernameChanged); -Snapshot.snapshotLocationSet.connect(snapshotLocationSet); + Entities.canRezChanged.connect(updatePrintPermissions); + Entities.canRezTmpChanged.connect(updatePrintPermissions); + GlobalServices.myUsernameChanged.connect(onUsernameChanged); + Snapshot.snapshotLocationSet.connect(snapshotLocationSet); + Window.snapshotShared.connect(snapshotUploaded); +} +startup(); -Entities.canRezChanged.connect(updatePrintPermissions); -Entities.canRezTmpChanged.connect(updatePrintPermissions); - -Script.scriptEnding.connect(function () { - if (buttonConnected) { - button.clicked.disconnect(onButtonClicked); - buttonConnected = false; - } - if (tablet) { - tablet.removeButton(button); - tablet.screenChanged.disconnect(onTabletScreenChanged); - } +function shutdown() { Window.snapshotShared.disconnect(snapshotUploaded); Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet); + GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); Entities.canRezChanged.disconnect(updatePrintPermissions); Entities.canRezTmpChanged.disconnect(updatePrintPermissions); -}); +} +Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 46ddeb2bab..804f838d04 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -1,9 +1,10 @@ "use strict"; -/*jslint vars:true, plusplus:true, forin:true*/ -/*global Window, Script, Tablet, HMD, Controller, Account, XMLHttpRequest, location, print*/ +/* jslint vars:true, plusplus:true, forin:true */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +/* global Window, Script, Tablet, HMD, Controller, Account, XMLHttpRequest, location, print */ // -// goto.js +// tablet-goto.js // scripts/system/ // // Created by Dante Ruiz on 8 February 2017 @@ -14,148 +15,118 @@ // (function () { // BEGIN LOCAL_SCOPE +var request = Script.require('request').request; +var AppUi = Script.require('appUi'); +var DEBUG = false; +function debug() { + if (!DEBUG) { + return; + } + print('tablet-goto.js:', [].map.call(arguments, JSON.stringify)); +} - var request = Script.require('request').request; - var DEBUG = false; - function debug() { - if (!DEBUG) { +var stories = {}, pingPong = false; +function expire(id) { + var options = { + uri: Account.metaverseServerURL + '/api/v1/user_stories/' + id, + method: 'PUT', + json: true, + body: {expire: "true"} + }; + request(options, function (error, response) { + debug('expired story', options, 'error:', error, 'response:', response); + if (error || (response.status !== 'success')) { + print("ERROR expiring story: ", error || response.status); + } + }); +} +var PER_PAGE_DEBUG = 10; +var PER_PAGE_NORMAL = 100; +function pollForAnnouncements() { + // We could bail now if !Account.isLoggedIn(), but what if we someday have system-wide announcments? + var actions = 'announcement'; + var count = DEBUG ? PER_PAGE_DEBUG : PER_PAGE_NORMAL; + var options = [ + 'now=' + new Date().toISOString(), + 'include_actions=' + actions, + 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), + 'require_online=true', + 'protocol=' + encodeURIComponent(Window.protocolSignature()), + 'per_page=' + count + ]; + var url = Account.metaverseServerURL + '/api/v1/user_stories?' + options.join('&'); + request({ + uri: url + }, function (error, data) { + debug(url, error, data); + if (error || (data.status !== 'success')) { + print("Error: unable to get", url, error || data.status); return; } - print('tablet-goto.js:', [].map.call(arguments, JSON.stringify)); - } - - var gotoQmlSource = "hifi/tablet/TabletAddressDialog.qml"; - var buttonName = "GOTO"; - var onGotoScreen = false; - var shouldActivateButton = false; - function ignore() { } - - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - var NORMAL_ICON = "icons/tablet-icons/goto-i.svg"; - var NORMAL_ACTIVE = "icons/tablet-icons/goto-a.svg"; - var WAITING_ICON = "icons/tablet-icons/goto-msg.svg"; - var button = tablet.addButton({ - icon: NORMAL_ICON, - activeIcon: NORMAL_ACTIVE, - text: buttonName, - sortOrder: 8 - }); - - function messagesWaiting(isWaiting) { - button.editProperties({ - icon: isWaiting ? WAITING_ICON : NORMAL_ICON - // No need for a different activeIcon, because we issue messagesWaiting(false) when the button goes active anyway. - }); - } - - function onClicked() { - if (onGotoScreen) { - // for toolbar-mode: go back to home screen, this will close the window. - tablet.gotoHomeScreen(); - } else { - shouldActivateButton = true; - tablet.loadQMLSource(gotoQmlSource); - onGotoScreen = true; - } - } - - function onScreenChanged(type, url) { - ignore(type); - if (url === gotoQmlSource) { - onGotoScreen = true; - shouldActivateButton = true; - button.editProperties({isActive: shouldActivateButton}); - messagesWaiting(false); - } else { - shouldActivateButton = false; - onGotoScreen = false; - button.editProperties({isActive: shouldActivateButton}); - } - } - button.clicked.connect(onClicked); - tablet.screenChanged.connect(onScreenChanged); - - var stories = {}, pingPong = false; - function expire(id) { - var options = { - uri: Account.metaverseServerURL + '/api/v1/user_stories/' + id, - method: 'PUT', - json: true, - body: {expire: "true"} - }; - request(options, function (error, response) { - debug('expired story', options, 'error:', error, 'response:', response); - if (error || (response.status !== 'success')) { - print("ERROR expiring story: ", error || response.status); + var didNotify = false, key; + pingPong = !pingPong; + data.user_stories.forEach(function (story) { + var stored = stories[story.id], storedOrNew = stored || story; + debug('story exists:', !!stored, storedOrNew); + if ((storedOrNew.username === Account.username) && (storedOrNew.place_name !== location.placename)) { + if (storedOrNew.audience === 'for_connections') { // Only expire if we haven't already done so. + expire(story.id); + } + return; // before marking } - }); - } - function pollForAnnouncements() { - // We could bail now if !Account.isLoggedIn(), but what if we someday have system-wide announcments? - var actions = 'announcement'; - var count = DEBUG ? 10 : 100; - var options = [ - 'now=' + new Date().toISOString(), - 'include_actions=' + actions, - 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), - 'require_online=true', - 'protocol=' + encodeURIComponent(Window.protocolSignature()), - 'per_page=' + count - ]; - var url = Account.metaverseServerURL + '/api/v1/user_stories?' + options.join('&'); - request({ - uri: url - }, function (error, data) { - debug(url, error, data); - if (error || (data.status !== 'success')) { - print("Error: unable to get", url, error || data.status); + storedOrNew.pingPong = pingPong; + if (stored) { // already seen return; } - var didNotify = false, key; - pingPong = !pingPong; - data.user_stories.forEach(function (story) { - var stored = stories[story.id], storedOrNew = stored || story; - debug('story exists:', !!stored, storedOrNew); - if ((storedOrNew.username === Account.username) && (storedOrNew.place_name !== location.placename)) { - if (storedOrNew.audience == 'for_connections') { // Only expire if we haven't already done so. - expire(story.id); - } - return; // before marking - } - storedOrNew.pingPong = pingPong; - if (stored) { // already seen - return; - } - stories[story.id] = story; - var message = story.username + " says something is happening in " + story.place_name + ". Open GOTO to join them."; - Window.displayAnnouncement(message); - didNotify = true; - }); - for (key in stories) { // Any story we were tracking that was not marked, has expired. - if (stories[key].pingPong !== pingPong) { - debug('removing story', key); - delete stories[key]; - } - } - if (didNotify) { - messagesWaiting(true); - if (HMD.isHandControllerAvailable()) { - var STRENGTH = 1.0, DURATION_MS = 60, HAND = 2; // both hands - Controller.triggerHapticPulse(STRENGTH, DURATION_MS, HAND); - } - } else if (!Object.keys(stories).length) { // If there's nothing being tracked, then any messageWaiting has expired. - messagesWaiting(false); - } + stories[story.id] = story; + var message = story.username + " " + story.action_string + " in " + + story.place_name + ". Open GOTO to join them."; + Window.displayAnnouncement(message); + didNotify = true; }); - } - var ANNOUNCEMENTS_POLL_TIME_MS = (DEBUG ? 10 : 60) * 1000; - var pollTimer = Script.setInterval(pollForAnnouncements, ANNOUNCEMENTS_POLL_TIME_MS); - - Script.scriptEnding.connect(function () { - Script.clearInterval(pollTimer); - button.clicked.disconnect(onClicked); - tablet.removeButton(button); - tablet.screenChanged.disconnect(onScreenChanged); + for (key in stories) { // Any story we were tracking that was not marked, has expired. + if (stories[key].pingPong !== pingPong) { + debug('removing story', key); + delete stories[key]; + } + } + if (didNotify) { + ui.messagesWaiting(true); + if (HMD.isHandControllerAvailable()) { + var STRENGTH = 1.0, DURATION_MS = 60, HAND = 2; // both hands + Controller.triggerHapticPulse(STRENGTH, DURATION_MS, HAND); + } + } else if (!Object.keys(stories).length) { // If there's nothing being tracked, then any messageWaiting has expired. + ui.messagesWaiting(false); + } }); +} +var MS_PER_SEC = 1000; +var DEBUG_POLL_TIME_SEC = 10; +var NORMAL_POLL_TIME_SEC = 60; +var ANNOUNCEMENTS_POLL_TIME_MS = (DEBUG ? DEBUG_POLL_TIME_SEC : NORMAL_POLL_TIME_SEC) * MS_PER_SEC; +var pollTimer = Script.setInterval(pollForAnnouncements, ANNOUNCEMENTS_POLL_TIME_MS); +function gotoOpened() { + ui.messagesWaiting(false); +} + +var ui; +var GOTO_QML_SOURCE = "hifi/tablet/TabletAddressDialog.qml"; +var BUTTON_NAME = "GOTO"; +function startup() { + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 8, + onOpened: gotoOpened, + home: GOTO_QML_SOURCE + }); +} + +function shutdown() { + Script.clearInterval(pollTimer); +} + +startup(); +Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index f339475f72..80ddbeca8b 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -306,7 +306,7 @@ } wantsMenu = clicked; }); - + clickMapping.from(Controller.Standard.Start).peek().to(function (clicked) { if (clicked) { //activeHudPoint2dGamePad(); diff --git a/tests-manual/render-perf/src/main.cpp b/tests-manual/render-perf/src/main.cpp index 9238e0dc1c..c9724ea352 100644 --- a/tests-manual/render-perf/src/main.cpp +++ b/tests-manual/render-perf/src/main.cpp @@ -659,7 +659,7 @@ private: update(); _initContext.makeCurrent(); - RenderArgs renderArgs(_renderThread._gpuContext, DEFAULT_OCTREE_SIZE_SCALE, 0, RenderArgs::DEFAULT_RENDER_MODE, + RenderArgs renderArgs(_renderThread._gpuContext, DEFAULT_OCTREE_SIZE_SCALE, 0, getPerspectiveAccuracyAngleTan(DEFAULT_OCTREE_SIZE_SCALE, 0), RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); QSize windowSize = _size; diff --git a/tests/shaders/src/ShaderTests.cpp b/tests/shaders/src/ShaderTests.cpp index 4dd15710f9..03dc034cd0 100644 --- a/tests/shaders/src/ShaderTests.cpp +++ b/tests/shaders/src/ShaderTests.cpp @@ -230,8 +230,8 @@ void ShaderTests::testShaderLoad() { auto glBackend = std::static_pointer_cast(_gpuContext->getBackend()); auto glshader = gpu::gl::GLShader::sync(*glBackend, *program); if (!glshader) { - qDebug() << "Failed to compile or link vertex " << vertexId << " fragment " << fragmentId; - continue; + qWarning() << "Failed to compile or link vertex " << vertexId << " fragment " << fragmentId; + QFAIL("Program link error"); } QVERIFY(glshader != nullptr); @@ -301,7 +301,7 @@ void ShaderTests::testShaderLoad() { QFAIL(error.what()); } - for (uint32_t i = 0; i <= maxShader; ++i) { + for (uint32_t i = 1; i <= maxShader; ++i) { auto used = usedShaders.count(i); if (0 != usedShaders.count(i)) { continue; diff --git a/tests/shared/src/AACubeTests.cpp b/tests/shared/src/AACubeTests.cpp index 95a4d7f9f0..4ed3ee2813 100644 --- a/tests/shared/src/AACubeTests.cpp +++ b/tests/shared/src/AACubeTests.cpp @@ -168,12 +168,13 @@ void AACubeTests::rayVsParabolaPerformance() { glm::vec3 origin(0.0f); glm::vec3 direction = glm::normalize(glm::vec3(1.0f)); + glm::vec3 invDirection = 1.0f / direction; float distance; BoxFace face; glm::vec3 normal; auto start = std::chrono::high_resolution_clock::now(); for (auto& cube : cubes) { - if (cube.findRayIntersection(origin, direction, distance, face, normal)) { + if (cube.findRayIntersection(origin, direction, invDirection, distance, face, normal)) { numRayHits++; } } diff --git a/tests/shared/src/GLMHelpersTests.cpp b/tests/shared/src/GLMHelpersTests.cpp index 93c4735a6d..71877e89f6 100644 --- a/tests/shared/src/GLMHelpersTests.cpp +++ b/tests/shared/src/GLMHelpersTests.cpp @@ -214,3 +214,39 @@ void GLMHelpersTests::testGenerateBasisVectors() { QCOMPARE_WITH_ABS_ERROR(w, z, EPSILON); } } + +void GLMHelpersTests::roundPerf() { + const int NUM_VECS = 1000000; + const float MAX_VEC = 500.0f; + std::vector vecs; + vecs.reserve(NUM_VECS); + for (int i = 0; i < NUM_VECS; i++) { + vecs.emplace_back(randFloatInRange(-MAX_VEC, MAX_VEC), randFloatInRange(-MAX_VEC, MAX_VEC), randFloatInRange(-MAX_VEC, MAX_VEC)); + } + std::vector vecs2 = vecs; + std::vector originalVecs = vecs; + + auto start = std::chrono::high_resolution_clock::now(); + for (auto& vec : vecs) { + vec = glm::round(vec); + } + + auto glmTime = std::chrono::high_resolution_clock::now() - start; + start = std::chrono::high_resolution_clock::now(); + for (auto& vec : vecs2) { + vec = glm::vec3(fastLrintf(vec.x), fastLrintf(vec.y), fastLrintf(vec.z)); + } + auto manualTime = std::chrono::high_resolution_clock::now() - start; + + bool identical = true; + for (int i = 0; i < vecs.size(); i++) { + identical &= vecs[i] == vecs2[i]; + if (vecs[i] != vecs2[i]) { + qDebug() << "glm: " << vecs[i].x << vecs[i].y << vecs[i].z << ", manual: " << vecs2[i].x << vecs2[i].y << vecs2[i].z; + qDebug() << "original: " << originalVecs[i].x << originalVecs[i].y << originalVecs[i].z; + break; + } + } + + qDebug() << "ratio: " << (float)glmTime.count() / (float)manualTime.count() << ", identical: " << identical; +} \ No newline at end of file diff --git a/tests/shared/src/GLMHelpersTests.h b/tests/shared/src/GLMHelpersTests.h index 030f2d477f..4d9bd0bb60 100644 --- a/tests/shared/src/GLMHelpersTests.h +++ b/tests/shared/src/GLMHelpersTests.h @@ -22,6 +22,7 @@ private slots: void testSixByteOrientationCompression(); void testSimd(); void testGenerateBasisVectors(); + void roundPerf(); }; float getErrorDifference(const float& a, const float& b);