Merge branch 'master' into stt_parenting2
1
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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true"/>
|
||||
<uses-feature android:name="android.hardware.sensor.gyroscope" android:required="true"/>
|
||||
|
||||
|
@ -75,6 +76,15 @@
|
|||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:process=":breakpad_uploader"/>
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.HeadsetStateReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.HEADSET_PLUG" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
<uses-feature android:name="android.software.vr.mode" android:required="true"/>
|
||||
|
|
|
@ -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<bool> 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<bool> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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<DomainAdapter.ViewHolder
|
|||
// references to our domains
|
||||
private Domain[] mDomains = {};
|
||||
|
||||
private static Domain[] DOMAINS_TMP_CACHE = {};
|
||||
|
||||
public DomainAdapter(Context c, String protocol, String lastLocation) {
|
||||
mContext = c;
|
||||
this.mInflater = LayoutInflater.from(mContext);
|
||||
mProtocol = protocol;
|
||||
mLastLocation = lastLocation;
|
||||
domainProvider = new UserStoryDomainProvider(mProtocol);
|
||||
loadDomains("", true);
|
||||
}
|
||||
|
||||
public void setListener(AdapterListener adapterListener) {
|
||||
mAdapterListener = adapterListener;
|
||||
}
|
||||
|
||||
public void startLoad() {
|
||||
useTmpCachedDomains();
|
||||
loadDomains("", true);
|
||||
}
|
||||
|
||||
private void useTmpCachedDomains() {
|
||||
synchronized (this) {
|
||||
if (DOMAINS_TMP_CACHE != null && DOMAINS_TMP_CACHE.length > 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<DomainAdapter.ViewHolder
|
|||
overrideDefaultThumbnails(domain);
|
||||
|
||||
mDomains = new Domain[domain.size()];
|
||||
mDomains = domain.toArray(mDomains);
|
||||
notifyDataSetChanged();
|
||||
if (mAdapterListener != null) {
|
||||
if (mDomains.length == 0) {
|
||||
mAdapterListener.onEmptyAdapter();
|
||||
} else {
|
||||
mAdapterListener.onNonEmptyAdapter();
|
||||
synchronized (this) {
|
||||
domain.toArray(mDomains);
|
||||
if (filterText.isEmpty()) {
|
||||
DOMAINS_TMP_CACHE = Arrays.copyOf(mDomains, mDomains.length);
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
if (mAdapterListener != null) {
|
||||
if (mDomains.length == 0) {
|
||||
mAdapterListener.onEmptyAdapter(true);
|
||||
} else {
|
||||
mAdapterListener.onNonEmptyAdapter(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,8 +140,6 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
|
|||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
// TODO
|
||||
//holder.thumbnail.setImageResource(mDomains[position].thumbnail);
|
||||
Domain domain = mDomains[position];
|
||||
holder.mDomainName.setText(domain.name);
|
||||
Uri uri = Uri.parse(domain.thumbnail);
|
||||
|
@ -164,8 +190,8 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
|
|||
}
|
||||
|
||||
public interface AdapterListener {
|
||||
void onEmptyAdapter();
|
||||
void onNonEmptyAdapter();
|
||||
void onEmptyAdapter(boolean shouldStopRefreshing);
|
||||
void onNonEmptyAdapter(boolean shouldStopRefreshing);
|
||||
void onError(Exception e, String message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,28 +37,57 @@ public class UserListAdapter extends RecyclerView.Adapter<UserListAdapter.ViewHo
|
|||
private ItemClickListener mClickListener;
|
||||
private AdapterListener mAdapterListener;
|
||||
|
||||
private static List<User> 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<User> 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<UserListAdapter.ViewHo
|
|||
}
|
||||
|
||||
public interface AdapterListener {
|
||||
void onEmptyAdapter();
|
||||
void onNonEmptyAdapter();
|
||||
void onEmptyAdapter(boolean shouldStopRefreshing);
|
||||
void onNonEmptyAdapter(boolean shouldStopRefreshing);
|
||||
void onError(Exception e, String message);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,4 +9,9 @@
|
|||
android:id="@+id/action_people"
|
||||
android:title="@string/people"
|
||||
/>
|
||||
<item
|
||||
android:id="@+id/action_debug_settings"
|
||||
android:title="@string/settings"
|
||||
android:visible="false"
|
||||
/>
|
||||
</menu>
|
||||
|
|
|
@ -29,4 +29,9 @@
|
|||
<string name="tagFragmentLogin">tagFragmentLogin</string>
|
||||
<string name="tagFragmentPolicy">tagFragmentPolicy</string>
|
||||
<string name="tagFragmentPeople">tagFragmentPeople</string>
|
||||
<string name="tagSettings">tagSettings</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="AEC">AEC</string>
|
||||
<string name="acoustic_echo_cancellation">Acoustic Echo Cancellation</string>
|
||||
<string name="settings_developer">Developer</string>
|
||||
</resources>
|
||||
|
|
11
android/app/src/main/res/xml/settings.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceCategory
|
||||
android:title="@string/settings_developer"
|
||||
android:key="pref_key_developer">
|
||||
<SwitchPreference
|
||||
android:key="aec"
|
||||
android:title="@string/AEC"
|
||||
android:summary="@string/acoustic_echo_cancellation" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
|
@ -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 = [
|
||||
|
|
|
@ -96,7 +96,6 @@ Agent::Agent(ReceivedMessage& message) :
|
|||
DependencyManager::set<recording::Recorder>();
|
||||
DependencyManager::set<recording::ClipCache>();
|
||||
|
||||
DependencyManager::set<ScriptCache>();
|
||||
DependencyManager::set<RecordingScriptingInterface>();
|
||||
DependencyManager::set<UsersScriptingInterface>();
|
||||
|
||||
|
@ -177,6 +176,8 @@ void Agent::run() {
|
|||
// Create ScriptEngines on threaded-assignment thread then move to main thread.
|
||||
DependencyManager::set<ScriptEngines>(ScriptEngine::AGENT_SCRIPT)->moveToThread(qApp->thread());
|
||||
|
||||
DependencyManager::set<ScriptCache>();
|
||||
|
||||
// make sure we request our script once the agent connects to the domain
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
@ -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<ScriptableAvatar>();
|
||||
// setup an Avatar for the script to use
|
||||
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
||||
|
||||
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<UsersScriptingInterface>().data());
|
||||
// give scripts access to the Users object
|
||||
_scriptEngine->registerGlobalObject("Users", DependencyManager::get<UsersScriptingInterface>().data());
|
||||
|
||||
auto player = DependencyManager::get<recording::Deck>();
|
||||
connect(player.data(), &recording::Deck::playbackStateChanged, [=] {
|
||||
if (player->isPlaying()) {
|
||||
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
|
||||
if (recordingInterface->getPlayFromCurrentLocation()) {
|
||||
scriptedAvatar->setRecordingBasis();
|
||||
auto player = DependencyManager::get<recording::Deck>();
|
||||
connect(player.data(), &recording::Deck::playbackStateChanged, [&player, &scriptedAvatar] {
|
||||
if (player->isPlaying()) {
|
||||
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
|
||||
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<RecordingScriptingInterface>();
|
||||
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<int16_t*>(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<AvatarHashMap>();
|
||||
_scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data());
|
||||
|
||||
// register ourselves to the script engine
|
||||
_scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this));
|
||||
|
||||
_scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data());
|
||||
_scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
|
||||
|
||||
QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor);
|
||||
_scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
|
||||
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
|
||||
_scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer);
|
||||
|
||||
_scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||
LocationScriptingInterface::locationSetter);
|
||||
|
||||
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
|
||||
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<AssignmentParentFinder>(_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<int16_t*>(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<AvatarHashMap>();
|
||||
_scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data());
|
||||
|
||||
// register ourselves to the script engine
|
||||
_scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this));
|
||||
|
||||
_scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data());
|
||||
_scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
|
||||
|
||||
QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor);
|
||||
_scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
|
||||
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
|
||||
_scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer);
|
||||
|
||||
_scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||
LocationScriptingInterface::locationSetter);
|
||||
|
||||
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
|
||||
_scriptEngine->registerGlobalObject("Recording", recordingInterface.data());
|
||||
|
||||
entityScriptingInterface->init();
|
||||
|
||||
_entityViewer.init();
|
||||
|
||||
entityScriptingInterface->setEntityTree(_entityViewer.getTree());
|
||||
|
||||
DependencyManager::set<AssignmentParentFinder>(_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<RecordingScriptingInterface>();
|
||||
setFinished(true);
|
||||
}
|
||||
|
||||
|
@ -859,17 +884,25 @@ void Agent::aboutToFinish() {
|
|||
DependencyManager::destroy<SoundCache>();
|
||||
DependencyManager::destroy<AudioScriptingInterface>();
|
||||
|
||||
DependencyManager::destroy<RecordingScriptingInterface>();
|
||||
DependencyManager::destroy<recording::Deck>();
|
||||
DependencyManager::destroy<recording::Recorder>();
|
||||
DependencyManager::destroy<recording::ClipCache>();
|
||||
DependencyManager::destroy<ScriptEngine>();
|
||||
|
||||
// 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<ScriptEngines>()->shutdownScripting();
|
||||
}
|
||||
|
||||
DependencyManager::destroy<ScriptEngines>();
|
||||
|
||||
DependencyManager::destroy<AssignmentDynamicFactory>();
|
||||
|
||||
DependencyManager::destroy<ScriptableAvatar>();
|
||||
|
||||
QMetaObject::invokeMethod(&_avatarAudioTimer, "stop");
|
||||
|
||||
// cleanup codec & encoder
|
||||
if (_codec && _encoder) {
|
||||
_codec->releaseEncoder(_encoder);
|
||||
|
|
|
@ -654,6 +654,15 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage>
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "AvatarMixerClientData.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
|
@ -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<QUuid> _radiusIgnoredOthers;
|
||||
std::vector<QUuid> _radiusIgnoredOthers;
|
||||
ConicalViewFrustums _currentViewFrustums;
|
||||
|
||||
int _recentOtherAvatarsInView { 0 };
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
@ -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<NodeList>();
|
||||
|
||||
|
@ -220,7 +234,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|
||||
_stats.nodesBroadcastedTo++;
|
||||
|
||||
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(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<AvatarSharedPointer> avatarsToSort;
|
||||
std::unordered_map<AvatarSharedPointer, SharedNodePointer> avatarDataToNodes;
|
||||
std::unordered_map<QUuid, uint64_t> 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<const AvatarMixerClientData*>(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<const AvatarMixerClientData*>(avatarNode->getLinkedData());
|
||||
assert(avatarNodeData); // we can't have gotten here without avatarNode having valid data
|
||||
const AvatarMixerClientData* avatarClientNodeData = reinterpret_cast<const AvatarMixerClientData*>(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<QUuid, uint64_t>::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<JointData>& 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<chrono::microseconds>(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<chrono::microseconds>(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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#
|
||||
|
||||
macro(AUTOSCRIBE_SHADER)
|
||||
message(STATUS "Processing shader ${SHADER_FILE}")
|
||||
unset(SHADER_INCLUDE_FILES)
|
||||
# Grab include files
|
||||
foreach(includeFile ${ARGN})
|
||||
|
|
|
@ -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()
|
|
@ -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<DomainServerNodeData*>(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;
|
||||
});
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
BIN
interface/resources/avatar/animations/jog_bwd.fbx
Normal file
BIN
interface/resources/avatar/animations/jump_standing_launch.fbx
Normal file
BIN
interface/resources/avatar/animations/run_bwd.fbx
Normal file
|
@ -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": []
|
||||
|
|
83
interface/resources/icons/tablet-icons/people-a-msg.svg
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 50 50"
|
||||
style="enable-background:new 0 0 50 50;"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="people-a.svg"><metadata
|
||||
id="metadata24"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs22" /><sodipodi:namedview
|
||||
pagecolor="#ff0000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview20"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#EF3B4E;}
|
||||
</style>
|
||||
<circle class="st1" cx="44.1" cy="6" r="5.6"/>
|
||||
<g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><circle
|
||||
class="st0"
|
||||
cx="25.6"
|
||||
cy="14.8"
|
||||
r="8.1"
|
||||
id="circle8"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M31.4,27h-11c-4.6,0-8.2,3.9-8.2,8.5v4.3c3.8,2.8,8.1,4.5,13.1,4.5c5.6,0,10.6-2.1,14.4-5.6v-3.2 C39.6,30.9,35.9,27,31.4,27z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><circle
|
||||
class="st0"
|
||||
cx="41.6"
|
||||
cy="17.1"
|
||||
r="3.5"
|
||||
id="circle12"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M43.9,23.9h-4.1c-0.9,0-1.7,0.4-2.3,1c1.2,0.6,2.3,1.6,3.1,2.5c1,1.2,1.5,2.7,1.7,4.3c0.3,0.9,0.4,1.8,0.2,2.8 v0.6c1.6-2.2,3.3-6,4-8.1v-0.3C46.5,25.7,45.3,23.9,43.9,23.9z"
|
||||
id="path14"
|
||||
style="fill:#000000;fill-opacity:1" /><circle
|
||||
class="st0"
|
||||
cx="9.4"
|
||||
cy="18"
|
||||
r="3.5"
|
||||
id="circle16"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M8.5,35.7c-0.1-0.7-0.1-1.4,0-2.1l0.1-0.2c0-0.2,0-0.4,0-0.6c0-0.2,0-0.3,0.1-0.5c0-0.2,0-0.3,0-0.5 c0.2-2.1,1.6-4.4,3.2-5.7c0.4-0.4,0.9-0.6,1.4-0.9c-0.5-0.5-1.3-0.7-2-0.7H7c-1.4,0-2.6,1.8-2.4,2.7v0.3 C5.1,29.8,6.8,33.5,8.5,35.7z"
|
||||
id="path18"
|
||||
style="fill:#000000;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 2.9 KiB |
24
interface/resources/icons/tablet-icons/people-i-msg.svg
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#EF3B4E;}
|
||||
</style>
|
||||
<circle class="st1" cx="44.1" cy="6" r="5.6"/>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<circle class="st0" cx="25.6" cy="14.8" r="8.1"/>
|
||||
<path class="st0" d="M31.4,27h-11c-4.6,0-8.2,3.9-8.2,8.5v4.3c3.8,2.8,8.1,4.5,13.1,4.5c5.6,0,10.6-2.1,14.4-5.6v-3.2
|
||||
C39.6,30.9,35.9,27,31.4,27z"/>
|
||||
<circle class="st0" cx="41.6" cy="17.1" r="3.5"/>
|
||||
<path class="st0" d="M43.9,23.9h-4.1c-0.9,0-1.7,0.4-2.3,1c1.2,0.6,2.3,1.6,3.1,2.5c1,1.2,1.5,2.7,1.7,4.3c0.3,0.9,0.4,1.8,0.2,2.8
|
||||
v0.6c1.6-2.2,3.3-6,4-8.1v-0.3C46.5,25.7,45.3,23.9,43.9,23.9z"/>
|
||||
<circle class="st0" cx="9.4" cy="18" r="3.5"/>
|
||||
<path class="st0" d="M8.5,35.7c-0.1-0.7-0.1-1.4,0-2.1l0.1-0.2c0-0.2,0-0.4,0-0.6c0-0.2,0-0.3,0.1-0.5c0-0.2,0-0.3,0-0.5
|
||||
c0.2-2.1,1.6-4.4,3.2-5.7c0.4-0.4,0.9-0.6,1.4-0.9c-0.5-0.5-1.3-0.7-2-0.7H7c-1.4,0-2.6,1.8-2.4,2.7v0.3
|
||||
C5.1,29.8,6.8,33.5,8.5,35.7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
5
interface/resources/images/eyeClosed.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="31" height="23" viewBox="0 0 31 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.59534 11.0156C6.16042 13.4128 9.65987 15.5898 13.6042 16.1774C17.686 16.7856 22.4164 15.7196 27.3057 11.0659C22.0721 6.07309 17.0642 5.14115 12.9153 5.90073C8.99427 6.61859 5.69298 8.87688 3.59534 11.0156ZM12.455 3.27591C17.7727 2.30235 23.9836 3.74895 30.1053 10.1333L31 11.0664L30.1053 11.9994C24.3636 17.9875 18.4774 19.5983 13.2276 18.8161C8.06048 18.0463 3.70384 14.9892 0.837069 11.9994L0 11.1265L0.778477 10.1986C3.05338 7.48717 7.2318 4.23217 12.455 3.27591Z" fill="#3D3D3D"/>
|
||||
<ellipse cx="15.6539" cy="10.9218" rx="3.65386" ry="3.81061" fill="#3D3D3D"/>
|
||||
<line x1="25" y1="2.12132" x2="7.12132" y2="20" stroke="#3D3D3D" stroke-width="3" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 825 B |
4
interface/resources/images/eyeOpen.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="31" height="16" viewBox="0 0 31 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.59534 8.01564C6.16042 10.4128 9.65987 12.5898 13.6042 13.1774C17.686 13.7856 22.4164 12.7196 27.3057 8.06585C22.0721 3.07309 17.0642 2.14115 12.9153 2.90073C8.99427 3.61859 5.69298 5.87688 3.59534 8.01564ZM12.455 0.275915C17.7727 -0.697651 23.9836 0.748949 30.1053 7.13329L31 8.06636L30.1053 8.99944C24.3636 14.9875 18.4774 16.5983 13.2276 15.8161C8.06048 15.0463 3.70384 11.9892 0.837069 8.99944L0 8.12646L0.778477 7.1986C3.05338 4.48717 7.2318 1.23217 12.455 0.275915Z" fill="#3D3D3D"/>
|
||||
<ellipse cx="15.644" cy="7.92179" rx="3.65386" ry="3.81061" fill="#3D3D3D"/>
|
||||
</svg>
|
After Width: | Height: | Size: 721 B |
BIN
interface/resources/images/interstitialPage/goTo_button.png
Normal file
After Width: | Height: | Size: 9 KiB |
BIN
interface/resources/images/loadingBar_placard.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
interface/resources/images/loadingBar_progress.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
interface/resources/meshes/redirect/oopsDialog_auth.fbx
Normal file
BIN
interface/resources/meshes/redirect/oopsDialog_auth.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
interface/resources/meshes/redirect/oopsDialog_protocol.fbx
Normal file
BIN
interface/resources/meshes/redirect/oopsDialog_protocol.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
interface/resources/meshes/redirect/oopsDialog_timeout.fbx
Normal file
BIN
interface/resources/meshes/redirect/oopsDialog_timeout.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
interface/resources/meshes/redirect/oopsDialog_vague.fbx
Normal file
BIN
interface/resources/meshes/redirect/oopsDialog_vague.png
Normal file
After Width: | Height: | Size: 5 KiB |
|
@ -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 {
|
||||
|
|
165
interface/resources/qml/AnimStats.qml
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -23,6 +23,7 @@ ModalWindow {
|
|||
objectName: "LoginDialog"
|
||||
implicitWidth: 520
|
||||
implicitHeight: 320
|
||||
closeButtonVisible: true
|
||||
destroyOnCloseButton: true
|
||||
destroyOnHidden: true
|
||||
visible: true
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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: "<a href='https://highfidelity.com/users/password/new'>Forgot Username?</a>"
|
||||
|
@ -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: "<a href='https://highfidelity.com/users/password/new'>Forgot Password?</a>"
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -24,6 +24,8 @@ Item {
|
|||
height: parent.height
|
||||
width: parent.width
|
||||
|
||||
property string title: "Controls"
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
TabBar {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
934
interface/resources/serverless/redirect.json
Normal file
|
@ -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
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
#include "AndroidHelper.h"
|
||||
#include <QDebug>
|
||||
#include <AudioClient.h>
|
||||
#include "Application.h"
|
||||
|
||||
#if defined(qApp)
|
||||
|
@ -18,6 +19,7 @@
|
|||
#define qApp (static_cast<Application*>(QCoreApplication::instance()))
|
||||
|
||||
AndroidHelper::AndroidHelper() {
|
||||
qRegisterMetaType<QAudio::Mode>("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<AudioClient>();
|
||||
if (audioClient) {
|
||||
QMetaObject::invokeMethod(audioClient.data(), "setHeadsetPluggedIn", Q_ARG(bool, pluggedIn));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<MessagesClient>();
|
||||
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<UserInputMapper>();
|
||||
DependencyManager::set<controller::ScriptingInterface, ControllerScriptingInterface>();
|
||||
DependencyManager::set<InterfaceParentFinder>();
|
||||
|
@ -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<ScriptCache>().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<OffscreenUi>()->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<bool>(AUTO_LOGOUT_SETTING_NAME, false).get();
|
||||
if (autoLogout) {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
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<AccountManager>();
|
||||
auto dialogsManager = DependencyManager::get<DialogsManager>();
|
||||
if (!accountManager->isLoggedIn()) {
|
||||
Setting::Handle<bool>{"loginDialogPoppedUp", false}.set(true);
|
||||
dialogsManager->showLoginDialog();
|
||||
QJsonObject loginData = {};
|
||||
loginData["action"] = "login dialog shown";
|
||||
UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData);
|
||||
}
|
||||
});
|
||||
Setting::Handle<bool>{"loginDialogPoppedUp", false}.set(false);
|
||||
checkLoginTimer->start();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -2410,6 +2462,8 @@ void Application::onAboutToQuit() {
|
|||
// so its persisted explicitly here
|
||||
Setting::Handle<QString>{ ACTIVE_DISPLAY_PLUGIN_SETTING_NAME }.set(getActiveDisplayPlugin()->getName());
|
||||
|
||||
Setting::Handle<bool>{"loginDialogPoppedUp", false}.set(false);
|
||||
|
||||
getActiveDisplayPlugin()->deactivate();
|
||||
if (_autoSwitchDisplayModeSupportedHMDPlugin
|
||||
&& _autoSwitchDisplayModeSupportedHMDPlugin->isSessionActive()) {
|
||||
|
@ -2489,6 +2543,11 @@ void Application::cleanupBeforeQuit() {
|
|||
}
|
||||
DependencyManager::destroy<ScriptEngines>();
|
||||
|
||||
bool autoLogout = Setting::Handle<bool>(AUTO_LOGOUT_SETTING_NAME, false).get();
|
||||
if (autoLogout) {
|
||||
DependencyManager::get<AccountManager>()->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<OffscreenUi>()->getSurfaceContext();
|
||||
surfaceContext->setContextProperty("Stats", Stats::getInstance());
|
||||
surfaceContext->setContextProperty("AnimStats", AnimStats::getInstance());
|
||||
|
||||
#if !defined(Q_OS_ANDROID)
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
|
@ -3437,6 +3505,18 @@ bool Application::isServerlessMode() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
void Application::setIsInterstitialMode(bool interstitialMode) {
|
||||
bool enableInterstitial = DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled();
|
||||
if (enableInterstitial) {
|
||||
if (_interstitialMode != interstitialMode) {
|
||||
_interstitialMode = interstitialMode;
|
||||
|
||||
DependencyManager::get<AudioClient>()->setAudioPaused(_interstitialMode);
|
||||
DependencyManager::get<AvatarManager>()->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<QString, QString> 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>();
|
||||
lodManager->setRenderTimes(presentTime, engineRunTime, gpuTime);
|
||||
lodManager->setRenderTimes(presentTime, engineRunTime, batchTime, gpuTime);
|
||||
lodManager->autoAdjustLOD(deltaTime);
|
||||
} else {
|
||||
DependencyManager::get<LODManager>()->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<AudioClient>().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<unsigned char*>(avatarPacket->getPayload());
|
||||
unsigned char* bufferStart = destinationBuffer;
|
||||
if (!isInterstitialMode()) {
|
||||
auto avatarPacket = NLPacket::create(PacketType::AvatarQuery);
|
||||
auto destinationBuffer = reinterpret_cast<unsigned char*>(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<NodeList>()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
|
||||
}
|
||||
|
||||
avatarPacket->setPayloadSize(destinationBuffer - bufferStart);
|
||||
|
||||
DependencyManager::get<NodeList>()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
|
||||
}
|
||||
|
||||
|
||||
|
@ -6272,6 +6357,7 @@ void Application::updateWindowTitle() const {
|
|||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
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<AddressManager>()->getDomainURL().toString();
|
||||
if (isInErrorState) {
|
||||
currentPlaceName = "serverless: " + nodeList->getDomainHandler().getErrorDomainURL().toString();
|
||||
} else {
|
||||
currentPlaceName = "serverless: " + DependencyManager::get<AddressManager>()->getDomainURL().toString();
|
||||
}
|
||||
} else {
|
||||
currentPlaceName = DependencyManager::get<AddressManager>()->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<AvatarManager>()->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<AudioClient>()->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<AvatarManager>()->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<ScriptEngines>()->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";
|
||||
|
|
|
@ -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<AvatarHashMap>()->getReplicaCount(); }
|
||||
void setOtherAvatarsReplicaCount(int count) { DependencyManager::get<AvatarHashMap>()->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<EntityItemID> pasteEntities(float x, float y, float z);
|
||||
bool exportEntities(const QString& filename, const QVector<EntityItemID>& 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<int, QKeyEvent> _keysPressed;
|
||||
|
||||
bool _enableProcessOctreeThread;
|
||||
bool _interstitialMode { false };
|
||||
|
||||
OctreePacketProcessor _octreeProcessor;
|
||||
EntityEditPacketSender _entityEditSender;
|
||||
|
|
|
@ -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<ModelEntityItem>(entity))->getRelayParentJoints()))
|
||||
&& (entity->getParentID() == DependencyManager::get<NodeList>()->getSessionUUID() || entity->getParentID() == DependencyManager::get<AvatarManager>()->getMyAvatar()->getSelfID());
|
||||
}
|
||||
|
||||
void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
myAvatar->removeAvatarEntities([&](const QUuid& entityID) {
|
||||
auto entity = entityTree->findEntityByID(entityID);
|
||||
return entity && isWearableEntity(entity);
|
||||
});
|
||||
|
||||
myAvatar->removeWearableAvatarEntities();
|
||||
addAvatarEntities(avatarEntities);
|
||||
}
|
||||
|
||||
|
@ -183,10 +172,7 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) {
|
|||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
myAvatar->removeAvatarEntities([&](const QUuid& entityID) {
|
||||
auto entity = entityTree->findEntityByID(entityID);
|
||||
return entity && isWearableEntity(entity);
|
||||
});
|
||||
myAvatar->removeWearableAvatarEntities();
|
||||
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
|
||||
myAvatar->useFullAvatarURL(avatarUrl);
|
||||
qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
|
||||
|
|
|
@ -11,16 +11,18 @@
|
|||
|
||||
#include "ConnectionMonitor.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "ui/DialogsManager.h"
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <DomainHandler.h>
|
||||
#include <AddressManager.h>
|
||||
#include <NodeList.h>
|
||||
|
||||
// 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<DialogsManager>()->setDomainConnectionFailureVisibility(true);
|
||||
connect(&_timer, &QTimer::timeout, this, [this]() {
|
||||
// set in a timeout error
|
||||
bool enableInterstitial = DependencyManager::get<NodeList>()->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<DialogsManager>()->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<DialogsManager>()->setDomainConnectionFailureVisibility(false);
|
||||
bool enableInterstitial = DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled();
|
||||
if (!enableInterstitial) {
|
||||
DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
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
|
||||
#endif // hifi_ConnectionMonitor_h
|
||||
|
|
|
@ -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<float> desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS);
|
||||
Setting::Handle<float> hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS);
|
||||
Setting::Handle<float> desktopLODDecreaseFPS("desktopLODDecreaseFPS", LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS);
|
||||
Setting::Handle<float> 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<DialogsManager>()->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;
|
||||
}
|
||||
|
|
|
@ -19,18 +19,14 @@
|
|||
#include <SimpleMovingAverage.h>
|
||||
#include <render/Args.h>
|
||||
|
||||
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
|
||||
|
|
|
@ -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<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
|
||||
auto hmd = DependencyManager::get<HMDScriptingInterface>();
|
||||
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<ModelBlender>().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<PickManager>().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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(avatar) {}
|
||||
glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); }
|
||||
float getRadius() const override { return std::static_pointer_cast<Avatar>(_avatar)->getBoundingRadius(); }
|
||||
uint64_t getTimestamp() const override { return std::static_pointer_cast<Avatar>(_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<Avatar> getAvatar() const { return _avatar; }
|
||||
private:
|
||||
AvatarSharedPointer _avatar;
|
||||
std::shared_ptr<Avatar> _avatar;
|
||||
};
|
||||
|
||||
auto avatarMap = getHashCopy();
|
||||
|
||||
const auto& views = qApp->getConicalViews();
|
||||
PrioritySortUtil::PriorityQueue<SortableAvatar> 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<NodeList>();
|
||||
AvatarHash::iterator itr = avatarMap.begin();
|
||||
while (itr != avatarMap.end()) {
|
||||
const auto& avatar = std::static_pointer_cast<Avatar>(*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<OtherAvatar>(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<NodeList>()->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<Avatar>(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<AvatarSharedPointer>::iterator avatarItr = _avatarsToFade.begin();
|
||||
const render::ScenePointer& scene = qApp->getMain3DScene();
|
||||
render::Transaction transaction;
|
||||
while (avatarItr != _avatarsToFade.end()) {
|
||||
auto avatar = std::static_pointer_cast<Avatar>(*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<UsersScriptingInterface>()->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<Avatar>(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<AvatarSharedPointer> removedAvatars;
|
||||
|
||||
{
|
||||
QWriteLocker locker(&_hashLock);
|
||||
|
||||
removedAvatars.reserve(_avatarHash.size());
|
||||
|
||||
auto avatarIterator = _avatarHash.begin();
|
||||
while (avatarIterator != _avatarHash.end()) {
|
||||
auto avatar = std::static_pointer_cast<Avatar>(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<OtherAvatar>(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<SortedAvatar> sortedAvatars;
|
||||
auto avatarHashCopy = getHashCopy();
|
||||
for (auto avatarData : avatarHashCopy) {
|
||||
auto avatar = std::static_pointer_cast<Avatar>(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<SortedAvatar> sortedAvatars;
|
||||
auto avatarHashCopy = getHashCopy();
|
||||
for (auto avatarData : avatarHashCopy) {
|
||||
auto avatar = std::static_pointer_cast<Avatar>(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<AvatarManager>()->getMyAvatar();
|
||||
|
||||
if (currentSessionUUID == myAvatar->getSessionUUID().toString()) {
|
||||
currentSessionUUID = "";
|
||||
}
|
||||
|
||||
|
||||
thisAvatarPalData.insert("sessionUUID", currentSessionUUID);
|
||||
thisAvatarPalData.insert("sessionDisplayName", avatar->getSessionDisplayName());
|
||||
thisAvatarPalData.insert("audioLoudness", avatar->getAudioLoudness());
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
#include "MyAvatar.h"
|
||||
#include "OtherAvatar.h"
|
||||
|
||||
using SortedAvatar = std::pair<float, std::shared_ptr<Avatar>>;
|
||||
|
||||
/**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<AvatarSharedPointer> _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;
|
||||
|
|
|
@ -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<ClientTraitsHandler>(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<glm::mat4>& matrixCache) {
|
||||
assert(QThread::currentThread() == thread());
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
|
@ -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<QUuid> idHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData"
|
||||
<< QString::number(avatarEntityIndex + 1) << "id", QUuid());
|
||||
_avatarEntityIDSettings.push_back(idHandle);
|
||||
Setting::Handle<QByteArray> 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<HMDScriptingInterface>();
|
||||
_avatarEntitiesLock.withReadLock([&] {
|
||||
for (auto entityID : _avatarEntityData.keys()) {
|
||||
if (hmdInterface->getCurrentTabletFrameID() == entityID) {
|
||||
// don't persist the tablet between domains / sessions
|
||||
continue;
|
||||
}
|
||||
QList<QUuid> avatarEntityIDs = _avatarEntityData.keys();
|
||||
unsigned int avatarEntityCount = avatarEntityIDs.size();
|
||||
unsigned int previousAvatarEntityCount = _avatarEntityCountSetting.get(0);
|
||||
resizeAvatarEntitySettingHandles(std::max<unsigned int>(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<bool> 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<bool(const QUuid& entityID)>& condition) {
|
||||
bool isWearableEntity(const EntityItemPointer& entity) {
|
||||
return entity->isVisible()
|
||||
&& (entity->getParentJointIndex() != INVALID_JOINT_INDEX
|
||||
|| (entity->getType() == EntityTypes::Model && (std::static_pointer_cast<ModelEntityItem>(entity))->getRelayParentJoints()))
|
||||
&& (entity->getParentID() == DependencyManager::get<NodeList>()->getSessionUUID()
|
||||
|| entity->getParentID() == AVATAR_SELF_ID);
|
||||
}
|
||||
|
||||
void MyAvatar::clearAvatarEntities() {
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
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<EntityTreeRenderer>();
|
||||
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>& attachmentData)
|
|||
}
|
||||
|
||||
// clear any existing avatar entities
|
||||
setAvatarEntityData(AvatarEntityMap());
|
||||
clearAvatarEntities();
|
||||
|
||||
for (auto& properties : newEntitiesProperties) {
|
||||
DependencyManager::get<EntityScriptingInterface>()->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<int> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|